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

例如,我想获取目录中所有文件的计数,这些文件都符合这个模式:log*


当前回答

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

下面的代码行定义了一个名为count的函数,该函数输出调用它的参数的数量。

count() { echo $#; }

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

count log*

为了在通配符模式没有匹配的情况下使结果正确,必须在展开时设置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,你可以按照以下方式进行hack:

# 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

其他回答

对于递归搜索:

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

这可以用标准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*

计数没有循环的非目录条目:

#!/bin/sh

# Creates strings of as many dots as expanded arguments

# dotted string for entries matching star pattern
star=$(printf '%.0s.' ./*)
# dotted string for entries matching star slash pattern (directories)
star_dir=$(printf '%.0s.' ./*/)
# dotted string for entries matching dot star pattern
dot_star=$(printf '%.0s.' ./.*)
# dotted string for entries matching dot star slash pattern (directories)
dot_star_dir=$(printf '%.0s.' ./.*/)

# Print pattern matches count excluding directories matches
printf 'Files count: %d\n' $((
  ${#star} - ${#star_dir} +
  ${#dot_star} - ${#dot_star_dir}
))

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

ls -1q log* | wc -l

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

输出被输送到wc -l,它计算行数。

你可以用bash安全地做到这一点(即不会被名称中有空格或\n的文件所bug):

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

您需要启用nullglob,以便在没有匹配的文件时不会在$logfiles数组中获得*.log文本。(参见如何“撤消”一个“set -x”?有关如何安全重置的示例。)

下面是一个可以在脚本中使用的通用Bash函数。

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

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