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

时间:2012-07-03 08:35:11

标签: bash

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

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

16 个答案:

答案 0 :(得分:155)

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

ls -1q log* | wc -l

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

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

答案 1 :(得分:42)

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

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

您需要启用nullglob,以便在没有文件匹配的情况下,您无法在*.log array中获得文字$logfiles。 (有关如何安全地重置它的示例,请参阅How to "undo" a 'set -x'?。)

答案 2 :(得分:41)

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

  • 包含空格,换行符或控制字符的文件名
  • 以连字符开头的文件名(想象一个名为-l的文件)
  • 隐藏文件,以点开头(如果glob为*.log而不是log*
  • 与glob匹配的目录(例如名为logs的匹配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

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

答案 3 :(得分:34)

对于递归搜索:

find . -type f -name '*.log' | wc -l

wc -w计算输出中的单词数量(bash会将*.log扩展为与该模式匹配的以空格分隔的文件列表),而wc -l将计算行数(find每行打印一个结果。)

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

find . -maxdepth 1 -type f -name '*.log' | wc -l 

答案 4 :(得分:7)

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

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

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

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

ls -l log* | wc -l

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

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

答案 5 :(得分:6)

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

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

这将找到所有匹配log *并且不以.*开头的文件 - “not name。*”是冗余的,但重要的是要注意“ls”的默认值不是显示点文件,但查找的默认值是包含它们。

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

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

答案 6 :(得分:6)

重要评论

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

这是 BUGGY

ls -1q some_pattern | wc -l

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

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

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

它完成了预期的工作。


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

答案 7 :(得分:4)

这是我的一个班轮。

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

答案 8 :(得分:1)

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

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

ls -R | grep php | wc -l // to find the files which contains the word php

您可以在grep上使用模式

答案 9 :(得分:1)

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

下面的行定义了一个名为count的函数,该函数显示已被调用的参数数量。

count() { echo $#; }

只需使用所需的模式进行调用:

count log*

为使glob模式不匹配时结果正确,必须在扩展发生时设置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*,或者如果您真的想避免产生子外壳,则可以按照以下方式破解某些内容:

# 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程序),它也可以由诸如findxargs之类的程序组成:

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

答案 10 :(得分:1)

我给了这个答案很多想法,尤其是考虑到don't-parse-ls stuff。一开始,我尝试过

<WARNING! DID NOT WORK>
du --inodes --files0-from=<(find . -maxdepth 1 -type f -print0) | awk '{sum+=int($1)}END{print sum}'
</WARNING! DID NOT WORK>

如果只有这样的文件名,则可以使用

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}'

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


注意,我在获得this answer的同时开始了这个思考过程。

答案 11 :(得分:1)

这是您可以在脚本中使用的通用Bash函数。

    # @see https://stackoverflow.com/a/11307382/430062
    function countFiles {
        shopt -s nullglob
        logfiles=($1)
        echo ${#logfiles[@]}
    }

    FILES_COUNT=$(countFiles "$file-*")

答案 12 :(得分:1)

这可以通过标准的POSIX Shell语法完成。

这是一个简单的count_entries函数:

#!/usr/bin/env sh

count_entries()
{
  # Emulating Bash nullglob 
  # If argument 1 is not an existing entry
  if [ ! -e "$1" ]
    # argument is a returned pattern
    # then shift it out
    then shift
  fi
  echo $#
}

简洁的定义:

count_entries(){ [ ! -e "$1" ]&&shift;echo $#;}

按类型提供的POSIX兼容文件计数器:

#!/usr/bin/env sh

count_files()
# Count the file arguments matching the file operator
# Synopsys:
# count_files operator FILE [...]
# Arguments:
# $1: The file operator
#   Allowed values:
#   -a FILE    True if file exists.
#   -b FILE    True if file is block special.
#   -c FILE    True if file is character special.
#   -d FILE    True if file is a directory.
#   -e FILE    True if file exists.
#   -f FILE    True if file exists and is a regular file.
#   -g FILE    True if file is set-group-id.
#   -h FILE    True if file is a symbolic link.
#   -L FILE    True if file is a symbolic link.
#   -k FILE    True if file has its `sticky' bit set.
#   -p FILE    True if file is a named pipe.
#   -r FILE    True if file is readable by you.
#   -s FILE    True if file exists and is not empty.
#   -S FILE    True if file is a socket.
#   -t FD      True if FD is opened on a terminal.
#   -u FILE    True if the file is set-user-id.
#   -w FILE    True if the file is writable by you.
#   -x FILE    True if the file is executable by you.
#   -O FILE    True if the file is effectively owned by you.
#   -G FILE    True if the file is effectively owned by your group.
#   -N FILE    True if the file has been modified since it was last read.
# $@: The files arguments
# Output:
#   The number of matching files
# Return:
#   1: Unknown file operator
{
  operator=$1
  shift
  case $operator in
    -[abcdefghLkprsStuwxOGN])
      for arg; do
        # If file is not of required type
        if ! test "$operator" "$arg"; then
          # Shift it out
          shift
        fi
      done
      echo $#
      ;;
    *)
      printf 'Invalid file operator: %s\n' "$operator" >&2
      return 1
      ;;
  esac
}

count_files "$@"

示例用法:

count_files -f log*.txt
count_files -d datadir*

答案 13 :(得分:0)

ls -1 log* | wc -l

这意味着每行列出一个文件,然后将其传递给字数命令命令,并将参数切换为计数行。

答案 14 :(得分:0)

这就是我一直在做的事情:

  

ls日志* | awk'END {print NR}'

答案 15 :(得分:-1)

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

ls | wc -l

要使用模式进行计数,请先管道传输到grep:

ls | grep log | wc -l