我正在查看一些用于获取目录中文件数量的shell代码。它的代码如下:
COUNT=$(ls -1 ${DIRNAME} | wc -l)
-1
这部分的意思是什么?我在其他问题中找不到关于这个的任何信息,只有遍历目录中文件的简单提及,这并不是我想要的。另外,从命令中移除它似乎没有影响。
COUNT=$(ls -1 ${DIRNAME} | wc -l)
以下是一种不稳定的统计目录中文件数量的方法:ls -1
告诉ls
不要把多个文件放在同一行中; 确保wc -l
通过计算行数来计算文件数。
现在,让我们谈谈“不稳定”:
ls
是实现定义的;某些版本可能会重复计算这类文件(GNU系统不会,但我不想打赌关于例如嵌入式路由器上漂浮的随机busybox
的发布)。${DIRNAME}
的未引用扩展允许将目录名字符串分割并在传递给ls
之前进行全局扩展,因此如果名称包含空格,则它可能成为多个参数。这应该是"$DIRNAME"
或"${DIRNAME}"
。此外,这是低效的,因为它使用多个外部工具(ls
和wc
)来完成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
(来自echo "$@" | wc
),以及从处理al | wc
中得到的argument list too long
(随后是来自wc
的3个零)。 - Jonathan Lefflerset --
存储在内存中,但不能在变量中使用array =(...)
。它是否确实试图支持这种说法? - 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.
)
COUNT
,但是shell变量和环境变量共享一个命名空间(Shell变量会自动覆盖/修改任何同名的环境变量),因此按照惯例应该首选小写名称。 - Charles Duffyls
的-1
参数)。 - Charles Duffy