除了对wc进行管道传输以计数目录中的文件之外,是否还有更简洁的选择


12

如果这样做ls -1 target_dir | wc -l,我会得到目录中文件的数量。我觉得这有点麻烦。有没有更优雅或更简洁的方式?


2
配管到wc时不需要“ -1”。
2014年

ls已经给出了总数,那又如何ls -l | head -1呢?如果您想要更短的内容,请使其成为别名。
丹尼尔·瓦格纳

2
@DanielWagner输出的“总计:nnn” ls -l表示文件的总大小,而不是文件的数量。
大卫·里希比

2
请记住,ls | wc -l如果任何文件名包含换行符,将给您错误的计数。
chepner 2014年

这取决于文件系统,并在目录中计算目录+ 2。答案有2个额外值(因为它本身及其父级都可以算)。 stat -c %h .提供的信息与ls -ld . | cut -d" " -f 2
ctrl-alt-delor

Answers:


12

假设bash 4+(任何受支持的Ubuntu版本都具有):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

称为num_files [dir]dir是可选的,否则它将使用当前目录。您的原始版本不计算隐藏文件,因此也不算。如果需要,请shopt -s dotglob在之前set -- *

您最初的示例不仅计算常规文件,还计算目录和其他设备-如果您确实只希望使用常规文件(包括指向常规文件的符号链接),则需要检查它们:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

如果您有GNU查找,也可以选择类似的方法(请注意,这包括隐藏文件,您的原始命令没有这样做):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(更改-type-xtype是否还希望计数到常规文件的符号链接)。


set如果文件太多,会不会失败?我认为您可能必须使用xargs和一些求和代码才能在一般情况下实现该功能。
l0b0

1
另外,shopt -s dotglob如果您希望将文件开头.进行计数
Digital Trauma

1
@ l0b0我认为set在这种情况下不会失败,因为我们实际上并不是在做exec。要晓得,我的系统上,getconf ARG_MAX得到262144,但如果我这样做test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max,它高兴地回复262145.
小次郎

@DavidRicherby -maxdepth不是POSIX。
克里斯·

4
@MichaelMartinez编写明显的代码不能代替编写正确的代码。
克里斯·

3

f=(target_dir/*);echo ${#f[*]}

对于名称中带有空格,换行符等的文件可以正常使用。


你能提供一些背景吗?这应该放在bash脚本中吗?
codecowboy

它可能。您也可以将其直接放在外壳中。该版本假定您需要当前目录;我已经对其进行了编辑,因此更接近您的问题。基本上,它会创建一个包含目录中所有文件的shell数组变量,然后输出该数组的计数。应该可以在具有数组的任何shell中工作-bash,ksh,zsh等-但可能不是普通的sh / ash / dash。
亚伦·戴维斯

2

ls仅当直接输出到终端时才是多列,您可以删除“ -1”选项,可以删除wc“ -l”选项,仅读取第一个值(惰性解,不用于合法证据,刑事调查,关键任务,战术行动)。

ls target | wc 

5
对于包含换行符的文件名,此操作将失败。
l0b0

@Emmanuel wc即使在平凡的情况下,您也需要解析您的结果以获取文件数,那么这怎么解决?
l0b0

@Emmanuel如果targetglob扩展后包含以连字符开头的某些内容,则此操作可能会失败。例如,创建一个新目录,进入该目录并执行touch -- {1,2,3,-a}.txt && ls *|wc(NB:用于rm -- *.txt删除那些文件。)
David Richerby 2014年

你是说wc -l吗 否则,您将获得输出的换行符,字数和字节数ls。大卫·里希比(David Richerby)就是这样说的:您必须再次解析它。
erik

@erik注释wc不带任何参数,如果您的大脑知道第一个参数是换行符,则无需解析。
伊曼纽尔

2

如果它的简洁你之后(而不是文件处理在他们的名字换行等时准确的正确性),我建议刚混叠是wc -llc(“行数”):

$ alias lc='wc -l'
$ ls target_dir|lc

正如其他人指出的那样,您无需-1选择ls,因为在ls写入管道时该选项是自动的。(除非您ls一直使用别名来始终使用列模式。我以前曾见过这种情况,但并不常见。)

一个lc别名是一般非常方便,而对于这个问题,如果你看一下“算当前目录”的情况下,ls|lc大约是简洁的,你可以得到。


2

到目前为止,亚伦的方法比您自己的方法更简洁。您的方法的更正确版本可能如下所示:

ls -aR1q | grep -Ecv '^\./|/$|^$'

该命令以递归方式列出所有文件(而不是目录),每行一个文件,其中包括当前目录下的.dotfiles(根据需要使用Shell Glob替换当前不可打印的字符)。grep会过滤掉任何父目录列表或..或* /或空行-因此每个文件应该只有一行-grep返回给您的总数。如果还希望包含子目录,请执行以下操作:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

-R如果您不希望递归结果,则在两种情况下都应删除。


1
我倾向于选择用这种方法find。如果您只想计数,那应该可以:(find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l删除深度控件以获得递归结果)。
亚伦·戴维斯

@AaronDavies-实际上不起作用。在任何这些文件名中添加换行符,然后自己查看。另外,要便携式执行同一操作,您需要:find . \! -name . -prune | wc -l-当然,这仍然行不通。
mikeserv

1
我不遵循- printf指令会打印一个不包含文件名的常量字符串(换行符),因此结果独立于任何奇怪的文件名。当然find,使用不支持的根本无法完成此技巧printf
亚伦·戴维斯

@AaronDavies-哦,是的。我认为已包含文件名。当然,它可以方便地完成:find .//. \!. -name . -prune | grep -c '^\.//\.'
mikeserv

辉煌!/是唯一无法在文件名中出现的其他字符,因此可以.//.确保每个文件序列只出现一次,对吧?尽管有几个问题-为什么.//.,为什么-prune?什么时候与find . \! -name . | grep -c '^\.'?(我假设.您的输入\!.是错字。)
亚伦·戴维斯
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.