1

I have a really easy question, I have found a bunch of similar questions answered but none that solved this for me.

I have a shell script that goes through a directory and prints out the number of files and directories in a sub directory, followed by the directory name.

However it fails with directories with spaces, it attempts to use each word as a new argument. I have tried putting $dir in quotations but that doesn't help. Perhaps because its already in the echo quotations.

for dir in `find . -mindepth 1 -maxdepth 1 -type d`
do
    echo -e "`ls -1 $dir | wc -l`\t$dir"
done

Thanks in advance for your help :)

Chris
  • 115
  • 2
  • 12
  • 1
    Whatever happened to `find . -mindepth ... | while read dir; do ...; done` *sigh* – robertklep May 05 '13 at 14:26
  • @robertklep Well -- that version isn't quite right; it mishandles some names with backslashes in them, names ending in IFS characters, and any name containing a newline. Also, any changes made to variables within the while loop are disregarded when it exits, on account of being on the right-hand-side of the pipeline. – Charles Duffy May 05 '13 at 14:28
  • 1
    @CharlesDuffy and the originally posted version (to which I'm responding) catches all those issues? ;) – robertklep May 05 '13 at 14:31
  • @CharlesDuffy This depends on the shell, `ksh` executes the last command of a pipe in the current shell. – Adrian Frühwirth May 05 '13 at 16:48
  • @robertklep No, but the point of responses here is to help people do things the right way. – Charles Duffy May 05 '13 at 22:10
  • @AdrianFrühwirth True for ksh and zsh, not true for ash, dash, bash (without the non-default `shopt -s lastpipe`), or bourne. – Charles Duffy May 05 '13 at 22:13
  • @CharlesDuffy indeed, and `for i in \`find ...\`` is not doing things the right way, which I was pointing out (in a comment, not an answer). – robertklep May 06 '13 at 05:54

1 Answers1

5

Warning: Two of the three code samples below use bashisms. Please take care to use the correct one if you need POSIX sh rather than bash.


Don't do any of those things. If your real problem does involve using find, you can use it like so:

shopt -s nullglob
while IFS='' read -r -d '' dir; do
  files=( "$dir"/* )
  printf '%s\t%s\n' "${#files[@]}" "$dir"
done < <(find . -mindepth 1 -maxdepth 1 -type d -print0)

However, for iterating over only immediate subdirectories, you don't need find at all:

shopt -s nullglob
for dir in */; do
  files=( "$dir"/* )
  printf '%s\t%s\n' "${#files[@]}" "$dir"
done

If you're trying to do this in a way compatible with POSIX sh, you can try the following:

for dir in */; do
  [ "$dir" = "*/" ] && continue
  set -- "$dir"/*
  [ "$#" -eq 1 ] && [ "$1" = "$dir/*" ] && continue
  printf '%s\t%s\n' "$#" "$dir"
done

You shouldn't ever use ls in scripts: http://mywiki.wooledge.org/ParsingLs

You shouldn't ever use for to read lines: http://mywiki.wooledge.org/DontReadLinesWithFor

Use arrays and globs when counting files to do this safely, robustly, and without external commands: http://mywiki.wooledge.org/BashFAQ/004

Always NUL-terminate file lists coming out of find -- otherwise, filenames containing newlines (yes, they're legal in UNIX!) can cause a single name to be read as multiple files, or (in some find versions and usages) your "filename" to not match the real file's name. http://mywiki.wooledge.org/UsingFind

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Awesome thanks :) As you can tell I am prety new to to this. I wonder if you could explain a couple lines to me? -- IFS='' read -r -d '' dir -- files=( "$dir"/* ) -- done < <(find . -mindepth 1 -maxdepth 1 -type d -print0) -- And also, it says nullglob is invalid option name – Chris May 05 '13 at 14:32
  • @Chris If it says nullglob is invalid, your shell probably isn't bash (and other parts of this also won't work). See the "choose your shell" section of mywiki.wooledge.org/BashGuide/Practices – Charles Duffy May 05 '13 at 14:50
  • @Chris I added a POSIX-compliant version of the answer; that one should work for you. – Charles Duffy May 05 '13 at 15:07
  • Any answer referencing Greg's wiki deserves a *+1*. – Adrian Frühwirth May 05 '13 at 16:55