The result of "ls | wc -l" does not match the real number of files

6,092

Solution 1

wc is a char, word, and line counter, not a file counter.

You, the programmer/script writer, are responsible for making it count what you want and to adjust the calculation accordingly.

In your case, you could do something like:

echo $((`ls|wc -l`-1))

Finally note that your ls is probably an alias as it gives a long listing which is not the normal ls without arguments. It may therefore be a good idea to refer to ls's full path (usually /bin/ls) to avoid confusion.

Solution 2

Just a extra info for the above,

You should use find instead of ls if you like to process the output, it has some futures which are more suitable (e.g. -print0) for piping the result to other applications.

In the above case you can use it like this,

find . -type f | wc -l

which will list any files on the current directory.

Solution 3

It's not working because wc -l returns the number of lines of the output of the ls command, which in this case includes total 44. Since your shell has an alias for ls as ls -cml, you're getting that extra information which is messing up your output.

Instead, use the command "ls" -Aq | wc -l. The -A command lists all files in the directory including dotfiles, but excludes . and ... The quotations here are important - they ignore the alias and run /bin/ls directly.

-q makes sure that file names are all printed on one line only even if they contain newline characters (which would then be rendered as ?).

Solution 4

As others have already mentioned, doing ls | wc -l is not always a reliable way to get files count in a directory.

Here are some reliable ways:

  • You can get find to print a . for each file found and get wc -l to count the number of lines:

     find . -type f -printf '.\n' | wc -l
    
  • If there are not many files in the directory, you can save the file names in an array and then get the length of the array:

     for f in *; do [ -f "$f" ] && files+=("$f"); done && echo "${#files[@]}"
    

    For all files and directories, this gets easier:

     files=( * ) && echo "${#files[@]}"
    

Example:

$ touch $'foo\nbar' 'foo bar' spam                         

$ ls | wc -l                      
4

$ find . -type f | wc -l                                   
4

$ find . -type f -printf '.\n' | wc -l
3

$ for f in *; do [ -f "$f" ] && files+=("$f"); done

$ echo "${#files[@]}"
3
Share:
6,092

Related videos on Youtube

user1420706
Author by

user1420706

Updated on September 18, 2022

Comments

  • user1420706
    user1420706 over 1 year

    I need to count the number of files under a folder and use the following command.

    cd testfolder
    bash-4.1$ ls | wc -l
    6
    

    In fact, there are only five files under this folder,

    bash-4.1$ ls
    total 44
    -rw-r--r-- 1 comp 11595 Sep  4 22:51 30.xls.txt
    -rw-r--r-- 1 comp 14492 Sep  4 22:51 A.pdf.txt
    -rw-r--r-- 1 comp  8160 Sep  4 22:51 comparison.docx.txt
    -rw-r--r-- 1 comp   903 Sep  4 22:51 Survey.pdf.txt
    -rw-r--r-- 1 comp  1206 Sep  4 22:51 Steam Table.xls.txt
    

    It looks like ls | wc -l even counts the total 44 as a file, which is not correct.

    • John1024
      John1024 over 7 years
      wc -l is working as it should. Please run the command type ls and report what you see.
    • user1420706
      user1420706 over 7 years
      the result of "type ls" is "ls is aliased to " ls -cml"
    • cutrightjm
      cutrightjm over 7 years
      It returns 6 because wc -l counts the number of lines... it's including the line that says total 44.
    • Sundeep
      Sundeep over 7 years
    • ilkkachu
      ilkkachu over 7 years
      also this and this
  • user1420706
    user1420706 over 7 years
    If I use /bin/ls | wc -l it will count 5 instead of 6
  • user1420706
    user1420706 over 7 years
    Also, echo $((ls|wc -l-1)) also give the correct count. Would you like to explain what does your command really do?
  • Julie Pelletier
    Julie Pelletier over 7 years
    It subtracts 1 from the result of wc on your aliased ls.
  • Julie Pelletier
    Julie Pelletier over 7 years
    If you put it in a script, it probably won't have the alias and as I mentioned it would be a better idea to use /bin/ls|wc -l.
  • Sundeep
    Sundeep over 7 years
    why not use -exec instead of piping the result...
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    Note that "ls" won't help if ls is defined as a function instead of an alias (or if there's also an alias for "ls" though that's less likely and not even supported by some shell). command ls may be more foolproof.
  • ilkkachu
    ilkkachu over 7 years
    Hmm... on bash: alias command="echo foo", command ls, outputs foo ls. Wonderful. At least it doesn't accept /bin/ls as an alias name.
  • Random832
    Random832 over 7 years
    @spasic because that would count the lines inside each file instead of number of files
  • Sundeep
    Sundeep over 7 years
    oops, good point :).. searching for find + wc gave this good Q&A - stackoverflow.com/questions/1412244/…
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @ikkachu, the idea is that it's common to have a function for ls (like ls() { [ ! -t 1 ] || set -- -F "$@"; command ls "$@"; }), while it's uncommon for command to be an alias (except on AT&T ksh where command is a builtin alias)
  • 123
    123 over 7 years
    Will break if filename contains newline.
  • roaima
    roaima over 7 years
    If you try ls | cat you'll see that ls automatically switches to ls -1 format when talking to something that isn't a terminal. Unless it's been overridden with an alias that stops it doing so. (Which is what was happening to the OP.)
  • badp
    badp over 7 years
    you can also say env ls. env doesn't know about your shell aliases, functions etc., and if you call it like that it's a no-op.
  • Rabin
    Rabin over 7 years
    @123, in this uniq case you can use find . -type f -print0 | wc --files0-from=- which use null as the delimiter.