I am looking at some shell code that is meant to get the count of the number of files in a directory. It reads:
COUNT=$(ls -1 ${DIRNAME} | wc -l)
What does the -1
part mean? I can't find anything about this in any other questions, just passing references to iterating over files in a directory which isn't what I am looking at. Also, removing it from the command seems to have no effect.
COUNT=$(ls -1 ${DIRNAME} | wc -l)
...is a buggy way to count files in a directory: ls -1
tells ls
not to put multiple files on a single line; making sure that wc -l
will then, by counting lines, count files.
Now, let's speak to "buggy":
ls
handles this is implementation-defined; some versions could double-count such files (GNU systems won't, but I wouldn't want to place bets about, say, random releases of busybox
floating around on embedded routers).${DIRNAME}
allows the directory name to be string-split and glob-expanded before being passed to ls
, so if the name contains whitespace, it can become multiple arguments. This should be "$DIRNAME"
or "${DIRNAME}"
instead....also, this is inefficient, as it invokes multiple external tools (ls
and wc
) to do something the shell can manage internally.
If you want something more robust, this version will work with all POSIX shells:
count_entries() { set -- "${1:-.}"/*; if [ -e "$1" ]; then echo "$#"; else echo 0; fi; }
count=$(count_entries "$DIRNAME") ## ideally, DIRNAME should be lower-case.
...or, if you want it to be faster-executing (not requiring a subshell), see the below (targeting only bash):
# like above, but write to a named variable, not stdout
count_entries_to_var() {
local destvar=$1
set -- "${2:-.}"/*
if [[ -e "$1" || -L "$1" ]]; then
printf -v "$destvar" %d "$#"
else
printf -v "$destvar" %d 0
fi
}
count_entries_to_var count "$DIRNAME"
...or, if you're targeting bash and don't want to bother with a function, you can use an array:
files=( "$DIRNAME"/* )
if [[ -e "${files[0]}" || -L "${files[0]}" ]]; then
echo "At least one file exists in $DIRNAME"
echo "...in fact, there are exactly ${#files[@]} files in $DIRNAME"
else
echo "No files exist in $DIRNAME"
fi
Finally -- if you want to deal with a list of file names too large to fit in memory, and you have GNU find
, consider using that:
find "$DIRNAME" -mindepth 1 -maxdepth 1 -printf '\n' | wc -l
...which avoids putting the names in the stream at all (and thus generates a stream for which one could simply measure length in bytes rather than number of lines, if one so chose).