3

I'm learning Linux through a gameshell.

Like the title says; I need to find the amount of sub directories and files in 'documents/'. The command runs fine but its the not the right answer. Here's my command :

find documents/ -type f | wc -l | awk '{print $1}' 

What am i doing wrong ?

  • Be aware that wc -l counts lines not files (filenames may contain newlines) – steeldriver Oct 19 at 12:53
  • I assumed it would count the lines in the files, thats why i used wc -l. Is there a better suited command ? – ForzaTekk Oct 19 at 13:00
  • If you want to count lines in the files that's a whole different thing – steeldriver Oct 19 at 13:06
  • it wouldn't be the equivalent in a certain way ? display all the lines in the files = the amount of sub directories ? – ForzaTekk Oct 19 at 13:08
2

Including -type f means it only covers files, and not directories. Try removing this part and see if works better.

And you might want to try removing | awk '{ print $1 }' to see if that part is truly necessary.

  • That would also make it find symbolic links etc. and would still mis-count files with embedded newlines. – Kusalananda Oct 20 at 8:06
  • Very true, although I doubt the gameshell referenced is digging that deep. – steve Oct 20 at 9:39
2

Your command only counts the number of lines outputted from find. The find command will only output the pathnames of regular files in or under the documents directory.

If you want to count the number of regular files and directories in or under your documents directory you may do this in the zsh shell:

set -- documents/**/*(DN/) documents/**/*(DN.)
printf 'There are %d regular files and directories in or under "documents"\n' "$#"

This sets the positional parameters ($1, $2, etc.) to the result of expanding the two filenames globbing patterns documents/**/*(DN/) and documents/**/*(DN.), the first of which will match all regular files while the second matches directories (no matter if they are hidden or not). The ** pattern matches "recursively" down into subdirectories.

Once the positional parameters have been set, the special variable $# will hold the number of these parameters.

In the bash shell, which does not support the glob qualifiers (DN/) and (DN.), you may use a loop:

shopt -s globstar dotglob nullglob

count=0
for pathname in documents/**/*; do
    if [[ -f $pathname ]] || [[ -d $pathname ]]; then
       [[ ! -h $pathname ]] && count=$(( count + 1 ))
    fi
done

printf 'There are %d regular files and directories in or under "documents"\n' "$count"

This is slightly more involved than the zsh variation, but should be easy to follow. We first set a number of shell options so that we can use **, so that we count hidden names, and so that the pattern resolves to nothing if there are no matches (these last two equates to the D and the N in the zsh glob qualifier).

Inside the loop, we test whether the current pathname is a regular file or a directory, and not a symbolic link. If it's a regular file or directory, we count it.

In sh, you have to resort to find:

find documents \( -type f -o -type d \) -exec echo . \; | wc -l

We look for regular files or directories in or under documents, and for each such thing we output a dot on a line by itself. The wc -l then counts the number of lines produced. This avoids counting filenames with embedded newlines multiple times.

Note that this variation will also count the documents directory itself.

  • Yes, I'm required to use find. Also how come when I try to find the amount of files and sub directories separately, it doesn't add up to the same total compared to ( -type f -o -type d ) . – ForzaTekk Oct 19 at 13:44
  • @ForzaTekk I can't make the find command count regular files and directories separately in such a way that the sum is different from what I'm getting when I do both at once. How much do your sums differ? – Kusalananda Oct 19 at 14:26
  • it differs by 4 ! I did them separately and came up with the good answer. But if I wanted to do both command in one it doesn't come up with the good answer. – ForzaTekk Oct 19 at 14:35
  • @ForzaTekk Well, assuming you are using the exact command I gave, there should be no difference. I can unfortunately not see what's happening on your screen. – Kusalananda Oct 19 at 14:56
0

The safest way to deal with files names that may contain spaces, newlines or anything is to convert each filename to a single safe character (like X).

With find we can execute a command for each file/directory found:

find documents/ -type f -exec printf X \; ; echo

That will generate an string with as many X as there are files (change f to d to count directories). To convert that to a number, we can capture the result of the command and print the size of the resulting string:

$ str="$(find documents -type d -exec printf X \;)"
$ echo "${#str}"
23

Or directly count bytes (equal to characters as the only character used is X):

$ find testit -type d -exec printf X \; | wc -c

The command find counts dot-files and could follow symlinks (with -L) or not (by default). If avoiding dot-files and following symbolic links (to files, not to directories since bash 4.3) by default is not a problem, we can use globbing

$ ( shopt -s globstar; set -- documents/**/*/; echo "$#" )
23

For directories, and:

$ ( shopt -s globstar; set -- documents/**/*; echo "$#" )
65

For all files and directories (not following directory symlinks).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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