我有一个文件夹,里面有一个巨大的文件列表,我想计算它。我正在这样做ls -l *.json | wc -l
,并且工作正常,直到列表明显变得足够大,以至于该命令不再起作用。
我后来尝试了一个ls -l | grep .json | wc -l
,效果非常好。
所以我想知道两者有什么区别?我认为这是因为后者不会ls
一次处理所有文件,而是将文件连续流式传输到 ,grep
然后传输到wc
?是这样吗,还是工作原理不同?
答案1
.json
如果您想计算当前目录中具有扩展名的非隐藏文件的数量,您可以这样做:
(){echo $#} *.json(NoN)
(N
对于nullglob
,oN
禁用我们在这里不需要的排序)。
ls -l | grep .json | wc -l
由于多种原因是错误的:
.
是匹配任何单个字符的正则表达式运算符。如果您想搜索为or或 的.json
字符串。grep -F .json
grep '\.json'
grep '[.]json'
- 这
Xjson
是在每行上寻找的。使用ls -l
,您可以打印文件名、用户名、组名、符号链接的目标,所有这些都可以包含Xjson
. - 文件名也可以包含除 0 和 of 之外的任何字节值
/
(链接目标可以包含/
);其中包括换行符。因此,如果您有一个名为 that 的文件xjson\nyjson
,则它是一个符号链接ajson\nbjson
,如果没有-q
,ls -l
该文件将打印 3 行,所有行都将包含Xjson
.如果某些文件名包含在当前语言环境中不形成有效字符的字节序列,您也可能会感到惊讶。 grep | wc -l
一般可以替换为grep -c
.
ls -l *.json | wc -l
更糟糕的是。旁边arg 列表太长潜在问题@L.ScottJohnson 已经指出,还有:
- 如果没有非隐藏
.json
文件,您还会收到0
一个错误,zsh
因为*.json
glob 无法匹配。 - 对于每个类型的参数目录,
ls -l
列出它们的内容,因此如果您有一个dir.json
目录,则其列表产生的所有行都将被计算在内。通常,您希望-d
在将 glob 扩展传递给 时使用 als
。 - 如果任何
.json
文件名以 开头-
,则该文件名将被视为选项ls
(特别是 GNU 或 busybox 实现,ls
即使在非选项参数之后也接受选项)。 - 如上所述,如果文件名或符号链接目标包含换行符,您将会遇到问题。
您可以使用以下方法修复大多数问题:
LC_ALL=C ls -qd -- *.json | wc -l
但在那里,所有实际工作都是由 shell 完成的。 shell 是一个扩展*.json
匹配文件列表并将其传递给ls
.ls
仅用于将每个打印在单独的行上以供输入,wc
以便可以对它们进行计数。ls
还做了很多不必要的工作,例如stat()
对每个进行系统调用以检查它是否存在(使用-l
,它会对lstat()
用户/组名称解析执行 a 和一些 uid/gid,以及readlink()
s 表示符号链接),并再次对列表进行排序( shell 已经对*.json
扩展进行了排序)。
shell 能够很好地自行计算该扩展。
对于(){echo $#} *.json(NoN)
,我们使用匿名函数,您也可以使用临时数组:files=(*.json(NoN)); echo $#files
。
另请注意,它只需要读当前目录的内容来构建该列表,它不需要像ls
以前那样尝试单独查找每个文件。
请注意,该语法特定于zsh
. POSIX 中的等价物sh
类似于:
set -- [*].json *.json
case $1$2 in
('[*].json*.json') shift 2;;
(*) shift;;
esac
echo "$#"
(文件列表位于"$@"
(已排序))。
答案2
如果扩展的文件列表太长,您的原始命令可能会失败,因为它依赖 shell 来扩展文件列表。
ls -l | grep .json | wc -l
没问题,因为命令行很短(没有扩展)并且 grep 会逐行进行过滤。
请注意,点匹配任何字符,而不仅仅是点。使用 -F 表示固定字符串:
ls -l | grep -F .json | wc -l
这样你就不会意外地匹配文件xjson
或类似的愚蠢的东西。
该命令仍然有一些注意事项,以及其他(更强大的)方法来实现您的预期目标,但除了这些问题之外,这就是两个命令之间的区别:按文件名通配符过滤(即 shell 扩展)与管道-to-grep