Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does -1 in "ls -1 path" mean?

Tags:

bash

shell

ls

glob

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.

like image 487
Matthew Herbst Avatar asked Dec 15 '22 10:12

Matthew Herbst


1 Answers

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":

  • Filenames can contain literal newlines. How a version of 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).
  • Unquoted expansion of ${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).

like image 74
Charles Duffy Avatar answered Dec 27 '22 12:12

Charles Duffy