“ls -1 path”中的-1是什么意思?

4

我正在查看一些用于获取目录中文件数量的shell代码。它的代码如下:

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

-1这部分的意思是什么?我在其他问题中找不到关于这个的任何信息,只有遍历目录中文件的简单提及,这并不是我想要的。另外,从命令中移除它似乎没有影响。


2
每个文件只输出一行。 - Charles Duffy
1
……是默认设置,当输出不是到终端时,但显式优于隐式。 - Charles Duffy
1
顺便提一下 - 可以查看 http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html 的第四段 - 大写字母的环境变量名称是保留的;应该首选带有小写字符的名称供应用程序使用。虽然你并不一定要导出 COUNT,但是shell变量和环境变量共享一个命名空间(Shell变量会自动覆盖/修改任何同名的环境变量),因此按照惯例应该首选小写名称。 - Charles Duffy
1
顺便提一下 -- 请参考 http://explainshell.com/explain?cmd=COUNT%3D%24%28ls+-1+%24%7BDIRNAME%7D+%7C+wc+-l%29 (您可以点击进入到您关心的嵌套级别,包括描述 ls-1 参数)。 - Charles Duffy
1
http://shellcheck.net/ 也是你的好朋友(可以找到未引用扩展错误,例如)。 - Charles Duffy
显示剩余6条评论
2个回答

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

以下是一种不稳定的统计目录中文件数量的方法:ls -1告诉ls不要把多个文件放在同一行中; 确保wc -l通过计算行数来计算文件数。

现在,让我们谈谈“不稳定”:

  • 文件名可以包含文字换行符。如何处理这个版本的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

...这样可以完全避免将名称放在流中(因此生成的流仅能通过测量以字节为单位的长度而不是行数来计算,如果有需要)。


2
你能否解释一下“bugginess”,并提出一个安全的方法来处理它? - Jonathan Leffler
1
谢谢。我注意到,如果由${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(来自echo "$@" | wc),以及从处理al | wc中得到的argument list too long(随后是来自wc的3个零)。 - Jonathan Leffler
1
@JonathanLeffler,由于参数列表从未被传递到外部命令,因此它不受execve()中存在的限制的影响; 相反,您只有为设置任何其他shell变量具有相同内存使用限制的情况。 - Charles Duffy
@JonathanLeffler,......有道理。无论如何--我稍微不清楚,从你的例子的草率阅读中,它是否旨在证明存在一系列名称,可以使用set --存储在内存中,但不能在变量中使用array =(...)。它是否确实试图支持这种说法? - Charles Duffy
1
@chepner,嗯。采用前者;后者的棘手之处在于它可能会重复计算,这取决于shell的初始状态(我们是否从一开始就隐藏点文件)。 - Charles Duffy
显示剩余3条评论

2
补充Charles Duffy的优秀回答他的回答中有一个特殊情况没有涉及到:如果第一个目录条目恰好是一个损坏的符号链接,使用-e进行通配符扩展的测试就不够了,因为Bash总是将存在性测试应用于符号链接的目标——在损坏符号链接的情况下,根据定义目标不存在。换句话说:对于损坏的符号链接,-e将指示错误,即使链接本身存在。因此,完全健壮的解决方案必须使用类似[[ -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.
)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接