是否有一个bash命令可以计数文件?


182

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

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

Answers:


242

这个简单的单行代码应该可以在任何shell中工作,而不仅仅是bash:

ls -1q log* | wc -l

ls -1q将为每个文件提供一行,即使它们包含空格或换行符之类的特殊字符。

输出通过管道传送到wc -l,后者对行数进行计数。


10
我不会使用-l,因为这需要stat(2)每个文件,并且出于计数目的不会添加任何内容。
camh 2012年

12
我不会使用ls,因为它会创建一个子进程。 log*由shell扩展,而不是由shell扩展ls,因此可以轻松echo完成。
cdarke

2
如果文件名带有空格或特殊字符,则回声将不起作用。
丹尼尔(Daniel)

4
@WalterTross是的(不是效率不是原始问题的要求)。我还发现,即使输出不是终端,-q也会使用换行符处理文件。我测试过的所有平台和外壳程序都支持这些标志。更新答案,谢谢您和卡姆的投入!
丹尼尔(Daniel)

3
如果有logs问题的目录中有一个目录,则该日志目录的内容也将计算在内。这可能不是故意的。
mogsie

54

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

$ shopt -s nullglob
$ logfiles=(*.log)
$ echo ${#logfiles[@]}

您需要启用,nullglob以便*.log在没有文件匹配的情况下不会在$logfiles 数组中获取文字。(有关如何安全重置它的示例,请参见如何“撤消”“ -x”设置。)


2
也许明确指出这是 Bash的答案,特别是对于尚未完全了解sh和bash区别的
Tripleee

另外,shopt -u nullglob如果nullglob未设置决赛,则应该跳过决赛。
Tripleee'9

注意:*.log用just 替换*将计数目录。如果您要枚举的文件具有传统的命名约定name.extension,请使用*.*
AlainD

52

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

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

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

ls 2>/dev/null -Ubad1 -- log* | wc -l

说明:

  • -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

最后一个在不使用大量内存的情况下处理了非常大的文件目录(尽管它确实使用了子外壳程序)。将-d不再是必要的,因为它仅列出当前目录的内容。


47

对于递归搜索:

find . -type f -name '*.log' -printf x | wc -c

wc -c将计算的输出中的字符数find,同时-printf x告诉每个结果find打印一个字符x

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

find . -maxdepth 1 -type f -name '*.log' -printf x | wc -c

6
即使没有带空格的文件,您的脚本的其他一些用户也可能会遇到恶意命名的文件,从而导致脚本失败。另外,在StackOverflow上遇到此问题的其他人可能拥有带有换行符的文件,并且需要了解陷阱。
mogsie

仅供参考,如果您只是简单地省略,-name '*.log'那么它将计算所有文件,这是我的用例所需要的。另外,-maxdepth标志非常有用,谢谢!
starmandeluxe

2
如果文件名中包含换行符,仍然会产生不正确的结果。解决方法很容易find;仅打印逐字文件名以外的其他内容。
Tripleee '18

8

这个问题的可接受答案是错误的,但是我的代表人数很少,因此无法对其添加评论。

Mat对这个问题的正确答案是:

shopt -s nullglob
logfiles=(*.log)
echo ${#logfiles[@]}

可接受的答案的问题在于wc -l计算换行符的数量,即使它们以'?在“ ls -l”的输出中。这意味着当文件名包含换行符时,接受的答案将失败。我已经测试了建议的命令:

ls -l log* | wc -l

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

touch log$'\n'def
ls log* -l | wc -l

6

如果您有很多文件,并且不想使用优雅的shopt -s nullglobbash数组解决方案,则可以使用find等,只要您不打印出文件名(其中可能包含换行符)即可。

find -maxdepth 1 -name "log*" -not -name ".*" -printf '%i\n' | wc -l

这将查找所有与log *匹配且不以.*- 开头的文件-“ not name。*”是多余的,但请注意,“ ls”的默认设置是不显示点文件,但默认设置寻找是将它们包括在内。

这是一个正确的答案,它可以处理您可以扔给它的任何类型的文件名,因为该文件名从未在命令之间传递。

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


您可能应该更新原始答案,而不是再次回答。
qodeninja

我认为使用findvs使用ls是解决问题的两种不同方式。 find并非总是存在于机器上,而是ls通常存在,
mogsie

2
但是,一盒没有猪油的猪油find可能并没有所有这些花哨的选择ls
Tripleee '18

1
还请注意,如果您在-maxdepth 1
Tripleee,

1
请注意,此解决方案将对隐藏目录中的文件进行计数。find默认情况下执行此操作。如果您没有意识到有一个隐藏的子文件夹,这可能会造成混乱,并且ls在某些情况下(默认情况下不会报告隐藏的文件)使用时可能会更有利。
MrPotatoHead

6

这是我的衬里。

 file_count=$( shopt -s nullglob ; set -- $directory_to_search_inside/* ; echo $#)

我花了一些时间来理解,但这很好!因此,set -- 除了为我们做准备之外,什么也不做$#,它存储了传递给shell程序的命令行参数的数量
xverges

@xverges是的,“ shopt -s nullglob”用于不计算隐藏文件(.files)。set-用于存储/设置位置参数的数量(在这种情况下,为文件数)。和#$用于显示位置参数的数量(文件数)。
zee

2

您可以使用-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

您可以在grep上使用模式


2

我已经给了这个答案很多想法,尤其是考虑到“ 不解析”之类的东西。一开始,我尝试

<警告!没工作>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</警告!没工作>

如果只有一个文件名像

touch $'w\nlf.aa'

但是如果我这样创建一个文件名则失败

touch $'firstline\n3 and some other\n1\n2\texciting\n86stuff.jpg'

我终于想出了我要介绍的内容。注意我试图获取目录中所有文件的计数(不包括任何子目录)。我认为,连同@Mat和@Dan_Yard的答案,以及至少具有@mogsie提出的大多数要求(我不确定内存。)我认为@mogsie的答案是正确的,但ls除非有特殊情况,否则我总是尽量避免解析。

awk -F"\0" '{print NF-1}' < <(find . -maxdepth 1 -type f -print0) | awk '{sum+=$1}END{print sum}'

更具可读性:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -print0) | \
    awk '{sum+=$1}END{print sum}'

这是专门针对文件的查找,使用空字符分隔输出(以避免空格和换行符问题),然后计算空字符的数量。文件的数量将比空字符的数量少一个,因为末尾将有一个空字符。

为了回答OP的问题,有两种情况需要考虑

1)非递归搜索:

awk -F"\0" '{print NF-1}' < \
  <(find . -maxdepth 1 -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

2)递归搜索。请注意,-name为了稍微不同的行为(隐藏文件等),可能需要更改参数内部的内容。

awk -F"\0" '{print NF-1}' < \
  <(find . -type f -name "log*" -print0) | \
    awk '{sum+=$1}END{print sum}'

如果有人想评论这些答案与我在此答案中提到的那些答案的比较,请这样做。


请注意,我在获得答案的同时进入了这一思考过程。


2

重要评论

(信誉不足,无法发表评论)

这是BUGGY

ls -1q some_pattern | wc -l

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

这是正确的,并且速度更快:

shopt -s nullglob; files=(some_pattern); echo ${#files[@]};

它完成了预期的工作。


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


1

这是我一直在做的事情:

ls log * | awk'END {print NR}'


awk 'END{print NR}'应该等于wc -l
musiphil

1

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

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

count() { echo $#; }

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

count log*

为使当globlob模式不匹配时结果正确,必须在扩展发生时设置shell选项nullglob(或failglob-这是zsh的默认行为)。可以这样设置:

shopt -s nullglob    # for sh / bash
setopt nullglob      # for zsh

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

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

( shopt -s nullglob ; shopt -u failglob ; count log* )

如果您想恢复轻量级语法count log*,或者如果您真的想避免产生子shell,则可以按照以下方式修改内容:

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

另外,该功能具有更广泛的用途。例如:

count a* b*          # count files which match either a* or b*
count $(jobs -ps)    # count stopped jobs (sh / bash)

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

find "$FIND_OPTIONS" -exec count {} \+    # count results of a search

0
ls -1 log* | wc -l

这意味着每行列出一个文件,然后将其通过管道切换到单词计数命令,并通过参数切换到计数行。


用管道输送ls输出时,不需要“ -1”选项。但是,如果没有文件与模式匹配,则可能要隐藏ls错误消息。我建议“ ls log * 2> / dev / null | wc -l”。
JohnMudd 2014年

丹尼尔的回答下的讨论在这里也很重要。当您没有用换行符匹配的目录或文件名时,这很好用,但是一个好的答案至少应该指出这些边界条件,而一个好的答案应该没有它们。许多错误是因为有人复制/粘贴了他们不了解的代码;因此指出这些缺陷至少可以帮助他们理解要注意的地方。(当然,还有更多的错误发生,因为他们忽略了后,他们认为该代码是为他们的目的很可能不够好改变了注意事项,然后的事情。)
tripleee

-1

要计数所有内容,只需将ls传递到字数统计行:

ls | wc -l

要计算模式,请先管道传输到grep:

ls | grep log | wc -l
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.