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

时间:2015-10-30 21:47:05

标签: bash shell glob ls

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.

2 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:2)

补充Charles Duffy's excellent answer
  他的答案没有涵盖的一个边缘案例:如果第一个目录条目恰好是损坏的符号链接,则使用-e测试glob扩展是还不够,因为Bash总是将存在测试应用于符号链接的目标 - 在符号链接损坏的情况下,根据定义它不存在。换句话说:对于损坏的符号链接,-e将指示 false ,即使链接本身存在。因此,完全可靠的解决方案必须使用类似[[ -e "$1" || -L "$1" ]]的内容 (-L测试其参数是否为符号链接,无论是否损坏。)

这里有一个略微更短的bash替代(使用子shell):

count=$(shopt -s nullglob; entries=(*); echo "${#entries[@]}")
    如果没有匹配,
  • shopt -s nullglob可确保模式扩展为空字符串。
  • entries=(*)收集数组中的所有匹配项(在当前目录中)
  • echo "${#entries[@]}"输出元素数组计数。
  • 由于不涉及任何外部实用程序,因此该命令getconf ARG_MAX限制,因此应使用大型目录。

请注意,以上是否计算隐藏.*)项也取决于dotglob选项的状态。 但是,很容易在命令中构建固定的隐藏项包含或不包含逻辑:

明确包含隐藏的内容:

count=$(shopt -s nullglob dotglob; entries=(*); echo "${#entries[@]}")

明确排除隐藏的项目:

count=$(shopt -s nullglob; shopt -u dotglob; entries=(*); echo "${#entries[@]}")

可以将所有上述内容包含在灵活的功能中

countEntries [<dir>] ... counts based on current state of the `dotglob` option
countEntries <dir> 0 ... counts non-hidden entries only
countEntries <dir> 1 ... counts all entries, including hidden ones
#!/usr/bin/env bash

# SYNOPSIS
#   countEntries [<dir> [<includeHidden>]]
# DESCRIPTION
#  <dir> defaults to .
#  <includeHidden> default to the current state of `shopt dotglob`;
#  a value of 0 explicitly EXcludes, 1 explicity INcludes hidden items.
countEntries() ( # Run entire function in subhell.
  local dir=${1:-.} includeHidden=$2 entries
  shopt -s nullglob
  case $includeHidden in
    0) # EXclude hidden entries
      shopt -u dotglob
      ;;
    1) # INclude hidden entries
      shopt -s dotglob
      ;;
    # Otherwise: use *current state* of `dotglob`
  esac  
  entries=("$1"/*) # Collect in array
  echo "${#entries[@]}" # Output count.
)