3

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.

marked as duplicate by Mikel, GAD3R, Stephen Harris, Thomas Dickey, Julie Pelletier Sep 6 '16 at 0:13

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 5
    wc -l is working as it should. Please run the command type ls and report what you see. – John1024 Sep 5 '16 at 4:11
  • 1
    the result of "type ls" is "ls is aliased to " ls -cml" – user288609 Sep 5 '16 at 4:16
  • 14
    It returns 6 because wc -l counts the number of lines... it's including the line that says total 44. – cutrightjm Sep 5 '16 at 4:23
  • 2
    see also: unix.stackexchange.com/questions/1125/… – Sundeep Sep 5 '16 at 4:43
  • 5
    also this and this – ilkkachu Sep 5 '16 at 8:03
8

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.

  • 1
    If I use /bin/ls | wc -l it will count 5 instead of 6 – user288609 Sep 5 '16 at 4:17
  • Also, echo $((ls|wc -l-1)) also give the correct count. Would you like to explain what does your command really do? – user288609 Sep 5 '16 at 4:19
  • Of course. I showed you 2 solutions to show you how you can deal with calculations in bash. – Julie Pelletier Sep 5 '16 at 4:19
  • It subtracts 1 from the result of wc on your aliased ls. – Julie Pelletier Sep 5 '16 at 4:21
  • 2
    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. – Julie Pelletier Sep 5 '16 at 4:22
11

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.

  • 1
    why not use -exec instead of piping the result... – Sundeep Sep 5 '16 at 6:47
  • 3
    @spasic because that would count the lines inside each file instead of number of files – Random832 Sep 5 '16 at 9:32
  • oops, good point :).. searching for find + wc gave this good Q&A - stackoverflow.com/questions/1412244/… – Sundeep Sep 5 '16 at 9:38
  • 1
    Will break if filename contains newline. – 123 Sep 5 '16 at 14:22
  • 1
    @123, in this uniq case you can use find . -type f -print0 | wc --files0-from=- which use null as the delimiter. – Rabin Sep 5 '16 at 22:04
9

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 ?).

  • 1
    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. – Stéphane Chazelas Sep 5 '16 at 7:10
  • 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. – ilkkachu Sep 5 '16 at 7:48
  • I find "ls" a bit unclear. /bin/ls explicitly conveys what you are doing. – Martin Nyolt Sep 5 '16 at 8:55
  • @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) – Stéphane Chazelas Sep 5 '16 at 10:01
  • 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. – badp Sep 5 '16 at 21:50
1

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
  • @roaima you know what you just made me feel like a stupid now, i don't know what i was thinking then. – heemayl Sep 5 '16 at 20:21
  • @roaima I have made an edit. All credit goes to you. Thanks. – heemayl Sep 5 '16 at 20:27
0
bash-4.1$ ls
1. total 44
2. -rw-r--r-- 1 comp 11595 Sep  4 22:51 30.xls.txt
3. -rw-r--r-- 1 comp 14492 Sep  4 22:51 A.pdf.txt
4. -rw-r--r-- 1 comp  8160 Sep  4 22:51 comparison.docx.txt
5. -rw-r--r-- 1 comp   903 Sep  4 22:51 Survey.pdf.txt
6. -rw-r--r-- 1 comp  1206 Sep  4 22:51 Steam Table.xls.txt

That's correct, there are 6 lines in the output. You might want to use: ls -1 which corresponds to single column format and it looks like this:

ls -1
Desktop
Documents
Downloads
Music
Pictures
Public
Templates
Videos

Now wc -l returns correct number of files:

ls -1 | wc -l
8
  • 2
    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.) – roaima Sep 5 '16 at 15:09

Not the answer you're looking for? Browse other questions tagged or ask your own question.