是否有一个bash命令来计算文件?

hud*_*udi 142 bash

是否有一个bash命令来计算与模式匹配的文件数?

例如,我想获取目录中与此模式匹配的所有文件的计数: log*

Dan*_*iel 191

这个简单的单行程应该适用于任何shell,而不仅仅是bash:

ls -1q log* | wc -l
Run Code Online (Sandbox Code Playgroud)

ls -1q将为每个文件提供一行,即使它们包含空格或特殊字符(如换行符).

输出通过管道输出到wc -l,它计算行数.

  • 我不会使用`ls`,因为它会创建一个子进程.`log*`由shell扩展,而不是`ls`,所以一个简单的`echo`就可以了. (11认同)
  • 我不会使用`-l`,因为它需要在每个文件上使用`stat(2)`并且为了计数的目的不会增加任何内容. (8认同)
  • @WalterTross这是真的(并不是效率是原始问题的要求).我还发现-q处理带换行符的文件,即使输出不是终端.我测试过的所有平台和shell都支持这些标志.更新答案,感谢您和camh的输入! (3认同)
  • 如果文件名带有空格或特殊字符,则回声将不起作用。 (2认同)
  • 如果在相关目录中有一个名为`logs`的目录,那么该logs目录的_contents_也将被计算在内.这可能不是故意的. (2认同)
  • @AerinmundFagelson在你的情况下,你有与“log*”模式匹配的目录(这与OP的情况不同),所以它们的内容也将被包括在内。您可以添加“-d”参数来阻止“ls”显示目录内容,例如:“ls -1qd” (2认同)

Mat*_*Mat 48

您可以\n使用bash 安全地执行此操作(即不会被带有空格或名称的文件窃听):

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}
Run Code Online (Sandbox Code Playgroud)

您需要启用nullglob,这样你就不会得到文字*.log$logfiles 数组,如果没有文件匹配.(有关如何安全地重置它的示例,请参见如何"撤消"'set -x'?

  • 也许明确指出这是一个 Bash-*only* 答案,特别是对于尚未完全了解 [sh 和 bash 之间的差异](/questions/5725296/difference-between-sh-and-) 的新访问者猛击) (3认同)

mog*_*sie 45

这里有很多答案,但有些人没有考虑到

  • 文件名中包含空格,换行符或控制字符
  • 以连字符开头的文件名(想象一个名为的文件-l)
  • 隐藏文件,以点开头(如果是glob而*.log不是log*
  • 与glob匹配的目录(例如logs,匹配的目录log*)
  • 空目录(即结果为0)
  • 非常大的目录(列出它们都会耗尽内存)

这是一个处理所有这些问题的解决方案:

ls 2>/dev/null -Ubad1 -- log* | wc -l
Run Code Online (Sandbox Code Playgroud)

说明:

  • -U导致ls不对条目进行排序,这意味着它不需要在内存中加载整个目录列表
  • -b为非图形字符打印C风格的转义,至关重要的是打印换行符\n.
  • -a打印出所有文件,甚至是隐藏文件(当glob log*表示没有隐藏文件时不严格需要)
  • -d打印出目录而不试图列出目录的内容,这是ls通常会做的
  • -1 确保它在一列上(ls在写入管道时会自动执行此操作,因此不一定非必要)
  • 2>/dev/null重定向stderr,以便如果有0个日志文件,请忽略该错误消息.(注意这shopt -s nullglob会导致ls列出整个工作目录.)
  • wc -l在生成目录列表时会消耗它,因此输出ls在任何时间点都不会在内存中.
  • --文件名与命令分开使用,--以免被理解为参数ls(如果log*删除)

shell 扩展log*到完整的文件列表,如果文件很多,可能会耗尽内存,因此通过grep运行它会更好:

ls -Uba1 | grep ^log | wc -l
Run Code Online (Sandbox Code Playgroud)

最后一个处理非常大的文件目录而不使用大量内存(虽然它确实使用了子shell).将-d不再是必要的,因为它仅列出当前目录的内容.

  • 我已经晚了将近 5 年,但我仍然想指出 `grep` 也可以计算行数,从而使 `wc -l` 变得不必要。生成的命令如下所示:`ls -Uba1 | grep -c ^log`。尽管如此,原来的答案非常有帮助。 (10认同)

Wil*_*den 39

试试这个:

find . -type f -name '*.log' -printf x | wc -c
Run Code Online (Sandbox Code Playgroud)

或者对于递归搜索:

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c
Run Code Online (Sandbox Code Playgroud)

wc -c计算输出中的单词数(bash将扩展find为与该模式匹配的以空格分隔的文件列表),同时-printf x计算行数(find每行打印一个结果).


更新:对于非递归搜索,请执行以下操作:

find . -type f -name '*.log' -printf x | wc -c
Run Code Online (Sandbox Code Playgroud)

这将绕过lanzz提到的空间问题.

  • 即使*您*没有带空格的文件,脚本的其他一些用户也可能会遇到恶意命名的文件,从而导致脚本失败.此外,在StackOverflow上遇到此问题的其他人可能会有包含换行符的文件,并且需要知道陷阱. (5认同)
  • 如果文件名中包含换行符,仍然会产生不正确的结果。使用`find`很容易解决。仅打印逐字文件名以外的其他内容。 (2认同)

Dan*_*ard 7

这个问题的接受答案是错误的,但我的代表性很低,所以无法添加评论.

Mat给出了这个问题的正确答案:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}
Run Code Online (Sandbox Code Playgroud)

接受的答案的问题是wc -l计算换行符的数量,即使它们以"?"打印到终端,也要对它们进行计数.在'ls -l'的输出中.这意味着当文件名包含换行符时,接受的答案FAILS.我测试了建议的命令:

ls -l log* | wc -l
Run Code Online (Sandbox Code Playgroud)

即使只有一个文件与名称恰好包含换行符的模式匹配,它也会错误地报告值2.例如:

touch log$'\n'def
ls log* -l | wc -l
Run Code Online (Sandbox Code Playgroud)


mog*_*sie 6

如果你有很多文件并且你不想使用优雅shopt -s nullglob和bash数组解决方案,只要不打印文件名(可能包含换行符),就可以使用find等.

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l
Run Code Online (Sandbox Code Playgroud)

这将找到所有匹配log*但不以.*- 开头的文件- "not name.*"是redunant,但重要的是要注意"ls"的默认值是不显示点文件,但是默认for find是包含它们.

这是一个正确的答案,并处理您可以抛出的任何类型的文件名,因为文件名永远不会在命令之间传递.

但是,shopt nullglob答案是最好的答案!

  • 但是,一盒没有"查找"的猪油也可能没有"ls"的所有那些花哨的选择. (2认同)
  • 请注意,此解决方案将在其计数中计算隐藏目录中的文件。`find` 默认执行此操作。如果人们没有意识到存在隐藏的子文件夹,这可能会造成混淆,并且在某些情况下使用 `ls` 可能会更有利,默认情况下它不会报告隐藏文件。 (2认同)

zee*_*zee 6

这是我的衬里。

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)
Run Code Online (Sandbox Code Playgroud)


Sma*_*Boy 6

重要评论

(没有足够的声誉发表评论)

这是BUGGY

ls -1q some_pattern | wc -l
Run Code Online (Sandbox Code Playgroud)

如果shopt -s nullglob碰巧被设置,它会打印所有常规文件的数量,而不仅仅是具有模式的文件(在 CentOS-8 和 Cygwin 上测试)。谁知道还有哪些其他无意义的错误ls

这是正确的,而且速度要快得多:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};
Run Code Online (Sandbox Code Playgroud)

它完成了预期的工作。


并且运行时间不同。
第一个:0.006在 CentOS 和0.083Cygwin 上(以防它被小心使用)。
第二个:0.000在 CentOS 和0.003Cygwin 上。


Rif*_*een 5

您可以使用 -R 选项来查找文件以及递归目录中的文件

ls -R | wc -l // to find all the files

ls -R | grep log | wc -l // to find the files which contains the word log
Run Code Online (Sandbox Code Playgroud)

您可以在 grep 上使用模式


Maë*_*lan 5

您可以使用 shell 函数轻松定义这样的命令。此方法不需要任何外部程序,也不会生成任何子进程。它不会尝试危险的ls解析,并且可以很好地处理 \xe2\x80\x9cspecial\xe2\x80\x9d 字符(空格、换行符、反斜杠等)。它仅依赖于shell提供的文件名扩展机制。它至少与 sh、bash 和 zsh 兼容。

\n\n

下面的行定义了一个名为的函数count,该函数打印调用该函数的参数数量。

\n\n
count() { echo $#; }\n
Run Code Online (Sandbox Code Playgroud)\n\n

只需使用所需的模式调用它:

\n\n
count log*\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了在通配模式不匹配时得到正确的结果,必须在扩展发生时设置shell 选项nullglob(或\xe2\x80\x94,这是 zsh 上的默认行为)。failglob可以这样设置:

\n\n
shopt -s nullglob    # for sh / bash\nsetopt nullglob      # for zsh\n
Run Code Online (Sandbox Code Playgroud)\n\n

根据您想要计算的内容,您可能还对 shell 选项感兴趣dotglob

\n\n

不幸的是,至少对于 bash,在本地设置这些选项并不容易。如果您不想全局设置它们,最直接的解决方案是以这种更复杂的方式使用该函数:

\n\n
( shopt -s nullglob ; shopt -u failglob ; count log* )\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果你想恢复轻量级语法count log*,或者如果你真的想避免生成子 shell,你可以按照以下方式进行一些修改:

\n\n
# sh / bash:\n# the alias is expanded before the globbing pattern, so we\n# can set required options before the globbing gets expanded,\n# and restore them afterwards.\ncount() {\n    eval "$_count_saved_shopts"\n    unset _count_saved_shopts\n    echo $#\n}\nalias count=\'\n    _count_saved_shopts="$(shopt -p nullglob failglob)"\n    shopt -s nullglob\n    shopt -u failglob\n    count\'\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

作为奖励,此功能具有更普遍的用途。例如:

\n\n
count a* b*          # count files which match either a* or b*\ncount $(jobs -ps)    # count stopped jobs (sh / bash)\n
Run Code Online (Sandbox Code Playgroud)\n\n

通过将函数转换为可从 PATH 调用的脚本文件(或等效的 C 程序),它也可以由诸如find和 之类的程序组成xargs

\n\n
find "$FIND_OPTIONS" -exec count {} \\+    # count results of a search\n
Run Code Online (Sandbox Code Playgroud)\n