如何报告所有子目录中的文件数?


24

我需要检查所有子目录并报告它们包含多少个文件(无需进一步递归):

directoryName1 numberOfFiles
directoryName2 numberOfFiles

为什么要find在Bash会用时使用?(shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done):对于所有目录,计算该目录中的条目数(包括隐藏的点文件,不包括...
janmoesen 2011年

@janmoesen你为什么不回答呢?我是Shell脚本的新手,但是您的方法看不到任何陷阱。对我来说,这似乎是最好的方法。没有人支持您的评论,但是也没有人评论为什么它可能不好。推荐的答案比您有更多的代表,因此让我想知道我是否缺少某些东西。
toxalot 2014年

@toxalot:我没有费心把它作为答案,因为它太短了(在语气上可能有些屈尊)。随时支持评论。:-)另外,关于“多少文件”的含义,这个问题有些含糊。我的解决方案计算“常规”文件目录;海报可能真的是“文件而不是目录”。要记住的另一件事是,这种阻塞不考虑“隐藏”的点文件。但是,围绕这两个陷阱有很多方法。但同样:不确定原始海报的确切要求。
janmoesen 2014年

Answers:


30

这是以安全且可移植的方式完成的。它不会被奇怪的文件名所迷惑。

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \; | wc -l && echo $f; done

请注意,它将首先打印文件数量,然后在单独的行上打印目录名称。如果您希望保留OP的格式,则需要进一步的格式化,例如

for f in *; do [ -d ./"$f" ] && find ./"$f" -maxdepth 1 -exec echo \;|wc -l|tr '\n' ' ' && echo $f; done|awk '{print $2"\t"$1}'

如果您有一组特定的子目录感兴趣,则可以将其替换*

为什么这样安全?(因此值得编写脚本)

文件名可以包含除以外的任何字符/。Shell或命令对一些字符进行了特殊处理。这些包括空格,换行符和破折号。

使用for f in *构造是获取每个文件名的安全方法,无论文件名包含什么。

将文件名包含在变量中后,您仍然必须避免使用find $f。如果$f包含文件名-testfind则会抱怨您刚刚提供的选项。避免这种情况的方法./是在名称前使用;这样,它具有相同的含义,但不再以短划线开头。

换行符和空格也是一个问题。如果$f包含“ hello,buddy”作为文件名find ./$f,则为find ./hello, buddy。您要告诉find您查看./hello,buddy。如果不存在,它将抱怨,并且永远不会进入./hello, buddy。这很容易避免-在变量周围使用引号。

最后,文件名可以包含换行符,因此无法计算文件名列表中的换行符;您会使用换行符为每个文件名获得额外的计数。为避免这种情况,请不要在文件列表中计算换行符;相反,计算代表单个文件的换行符(或其他任何字符)。这就是find命令简单-exec echo \;而没有的原因-exec echo {} \;。我只想打印一个新行来计算文件。


1
为什么世界上有人在文件名中使用换行符?感谢您的回答。
2011年

1
我相信文件名可以包含/以外的任何字符和空字符。dwheeler.com/essays/fixing-unix-linux-filenames.html
Flimm

2
计数将包括目录本身。如果您想将其排除在外,请使用-mindepth 1
toxalot

您也可以使用-printf '\n'代替-exec echo
toxalot

1
@toxalot,如果您有一个支持的查找,-printf则可以,但是如果您希望它在FreeBSD上运行,则不可以。
肖恩·高夫

6

假设您正在寻找一个标准的Linux解决方案,一个相对简单的方法是find

find dir1/ dir2/ -maxdepth 1 -type f | wc -l

find遍历两个指定的子目录的地方,到-maxdepth的1 中将阻止进一步的递归,并且仅报告-type f用换行符分隔的文件()。然后将结果通过管道传递wc以计数那些行的数量。


我有2个以上的目录...如何将您的命令与find . -maxdepth 1 -type d输出合并?
2011年

您可以(a)将所需目录包含在变量中,find $dirs ...或者(b)如果它们仅位于一个更高级别的目录中,则该目录中的globfind */ ...
jasonwryan 2011年

1
如果任何文件名中包含换行符,这将报告错误的结果。
肖恩·高夫

@肖恩:谢谢。我以为文件名中包含空格,但没有考虑换行:有任何修复建议吗?
jasonwryan 2011年

添加-exec echo到您的find命令-这样,它就不会回显文件名,而只是换行。
肖恩·高夫

5

“无递归”是指如果directoryName1具有子目录,那么您不想计算子目录中的文件吗?如果是这样,这是一种对指定目录中的所有常规文件进行计数的方法:

count=0
for d in directoryName1 directoryName2; do
  for f in "$d"/* "$d"/.[!.]* "$d"/..?*; do
    if [ -f "$f" ]; then count=$((count+1)); fi
  done
done

请注意,该-f测试执行两个功能:测试与上述glob之一匹配的条目是否为常规文件,并且测试该条目是否为匹配项(如果其中一个glob不匹配,则模式保持不变¹)。如果要计算给定目录中的所有条目,而不论其类型如何,请替换-f-e

Ksh可以使模式与点文件匹配,并在没有文件与模式匹配的情况下生成空列表。因此,在ksh中,您可以像这样计数常规文件:

FIGNORE='.?(.)'
count=0
for x in ~(N)directoryName1/* ~(N)directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

或所有文件,如下所示:

FIGNORE='.?(.)'
files=(~(N)directoryName1/* ~(N)directoryName2/*)
count=${#files}

Bash有多种方法可以简化此过程。要计算常规文件:

shopt -s dotglob nullglob
count=0
for x in directoryName1/* directoryName2/*; do
  if [ -f "$x" ]; then ((++count)); fi
done

要计算所有文件:

shopt -s dotglob nullglob
files=(directoryName1/* directoryName2/*)
count=${#files}

像往常一样,在zsh中它甚至更简单。要计算常规文件:

files=({directoryName1,directoryName2}/*(DN.))
count=$#files

更改(DN.)(DN)计算所有文件。

¹ 请注意,每个模式都匹配,否则结果可能会不正确(例如,如果您要计算以数字开头的文件,则不能仅仅这样做,for x in [0-9]*; do if [ -f "$x" ]; then …因为可能有一个名为的文件[0-9]foo)。


2

基于计数脚本Shawn的答案和Bash技巧,可以确保即使使用换行符的文件名也以可用形式打印在一行上:

for f in *
do
    if [ -d "./$f" ]
    then
        printf %q "$f"
        printf %s ' '
        find "$f" -maxdepth 1 -printf x | wc -c
    fi
done

printf %q是打印带引号的字符串版本,即单行字符串,您可以将其放入Bash脚本中,以解释为包含(可能是)换行符和其他特殊字符的文字字符串。例如,看到echo -n $'\tfoo\nbar'VS printf %q $'\tfoo\nbar'

find命令的工作原理是简单地为每个文件打印一个字符,然后对这些字符进行计数而不是对行进行计数。


1

这里的“蛮力” -ish办法让你的结果,使用findecholswcxargsawk

find . -maxdepth 1 -type d -exec sh -c "echo '{}'; ls -1 '{}' | wc -l" \; | xargs -n 2 | awk '{print $1" "$2}'

这项工作。但是,如果名称中包含``空格的目录则输出混乱。
2011年

如果任何文件名中包含换行符,这将报告错误的结果。
肖恩·高夫

-1
for i in *; do echo $i; ls $i | wc -l; done

4
欢迎来到U&L。答案应该是带有解释的长格式,而不仅仅是代码滴。请扩展此内容并说明发生了什么。同样,这是一种非常低效的方法,例如,不处理带有空格的文件。
slm

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.