【问题标题】:What does -1 in "ls -1 path" mean?“ls -1 路径”中的 -1 是什么意思?
【发布时间】:2023-12-22 07:04:01
【问题描述】:

我正在查看一些用于计算目录中文件数量的 shell 代码。上面写着:

COUNT=$(ls -1 ${DIRNAME} | wc -l)

-1 部分是什么意思?我在任何其他问题中都找不到关于此的任何内容,只是传递对迭代目录中文件的引用,这不是我正在查看的内容。此外,从命令中删除它似乎没有任何效果。

【问题讨论】:

  • 每个文件输出一行。
  • ...是的,当输出不是到终端时,这是默认设置,但显式优于隐式。
  • BTW -- 参见pubs.opengroup.org/onlinepubs/009695399/basedefs/… 的第四段 -- 保留所有大写的环境变量名;应用程序使用时应首选带有小写字符的名称。当然,您不一定是 exporting COUNT,但 shell 变量和环境变量共享一个命名空间(shell 变量将自动覆盖/修改任何类似命名的环境变量),因此按照惯例应该首选小写名称。
  • 顺便说一句——见explainshell.com/…(您可以点击进入您关心的嵌套级别,包括将-1参数描述为ls)。
  • ...shellcheck.net 也是你的朋友(会发现未引用的扩展错误,f'rinstance)。

标签: bash shell glob ls


【解决方案1】:
COUNT=$(ls -1 ${DIRNAME} | wc -l)

...对目录中的文件进行计数是一种错误的方法:ls -1 告诉ls 不要将多个文件放在一行中;确保 wc -l 然后通过计算行数来计算文件数。

现在,让我们谈谈“buggy”:

  • 文件名可以包含文字换行符。 ls 的版本如何处理这是实现定义的;某些版本可能会重复计算此类文件(GNU 系统不会,但我不想押注,例如,随机发布的 busybox 在嵌入式路由器上浮动)。
  • ${DIRNAME} 的无引号扩展允许目录名称在传递给ls 之前进行字符串拆分和全局扩展,因此如果名称包含空格,它可以成为多个参数。这应该是 "$DIRNAME""${DIRNAME}"

...同样,这也是低效的,因为它会调用多个外部工具(lswc)来做一些 shell 可以在内部管理的事情。


如果您想要更健壮的东西,这个版本将适用于所有 POSIX shell:

count_entries() { set -- "${1:-.}"/*; if [ -e "$1" ]; then echo "$#"; else echo 0; fi; }
count=$(count_entries "$DIRNAME") ## ideally, DIRNAME should be lower-case.

...或者,如果您希望它执行得更快(不需要子 shell),请参见以下内容(仅针对 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"

...或者,如果您的目标是 bash 并且不想打扰函数,则可以使用数组:

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

最后——如果你想处理一个太大而无法放入内存的文件名列表,并且你有 GNU find,请考虑使用它:

find "$DIRNAME" -mindepth 1 -maxdepth 1 -printf '\n' | wc -l

...这完全避免了将名称放入流中(从而生成一个流,可以简单地测量字节长度而不是行数,如果这样选择的话)。

【讨论】:

  • 您能解释一下错误并提出安全的方法吗?
  • 谢谢。我观察到如果${DIRECTORY} 表示的字符串很长,那么在极端情况下,您可能会因为名称太长而遇到问题。我注意到您可以使用 set -- 设置更长的参数列表,而不是使用后续外部命令处理:set -- $(perl -e 'for $i (1 .. 1000) { printf "%.5d-%s.txt\n", $i, "ABC" x 256; }'); echo $#; echo "$@" | wc; al "$@" | wc 在 Mac OS X 10.10 Yosemite 上,这会产生 1000(来自 echo $#),1 1000 779000 来自 @ 987654344@ 和 argument list too long 来自处理 al | wc(后跟来自 wc 的 3 个零)。
  • @JonathanLeffler,因为参数列表永远不会被传递给外部命令,所以它不受 execve() 此处的限制;相反,对于设置任何其他 shell 变量,只有相同的内存使用限制。
  • 在我的机器上,al 是一个外部程序,实际上是用 C 语言编写的,但等效于 printf "%s\n" "$@"al 表示“参数列表”,每个参数单独列出)。它代表您选择的外部程序:例如cat,或ls。在此示例中,您非常安全,因为您没有调用外部程序。在其他情况下,$1/* 表示法可能有问题。
  • @chepner,嗯。采用前者;后者的棘手之处在于它可能会重复计算,具体取决于 shell 的初始状态(我们是否一开始就隐藏点文件)。
【解决方案2】:

补充Charles Duffy's excellent answer

他的回答没有涵盖一个极端情况:如果第一个目录条目恰好是一个损坏的符号链接,那么使用-e 测试全局扩展是不够的,因为Bash 总是将存在性测试应用于符号链接的 target - 在损坏的符号链接的情况下,根据定义不存在。换句话说:对于损坏的符号链接,-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.
)

【讨论】:

    最近更新 更多