报告子目录、Bash 中的文件数量

报告子目录、Bash 中的文件数量

我在 Win10 计算机上工作,但我通常在 Gitbash 或 linux 子系统中工作。

我正在尝试获取指定目录的所有子目录中的文件数。

这是一个类似的问题如何报告所有子目录中的文件数量?但不同之处在于,我在所有子目录上都没有恒定数量的级别,我有类似的内容:

Dir1/sub1
Dir1/sub1/subsub1
Dir1/sub2
Dir1/sub3/subsub3/subsubsub3

我试过

 shopt -s dotglob; for dir in */; do all=("$dir"/*); echo "$dir: ${#all[@]}"; done

调整要搜索的级别数(* /、* /* /* 等)

但我无法真正得到我想要的东西,例如:

Dir1/sub1: Number of files
Dir1/sub2: Number of files
Dir1/sub3: Number of files

答案1

#!/bin/bash

shopt -s dotglob nullglob

topdir='./Dir1'

for subdir in "$topdir"/*/; do
    find "$subdir" -type f -exec echo . \; |
    printf '%s: %d\n' "${subdir%/}" "$( wc -l )"
done

这个小bash脚本将输出子目录的路径名列表,$topdir后跟每个子目录下(任何位置)找到的常规文件的数量。

该脚本循环遍历所有子目录$topdir,并为每个子目录运行find命令

find "$subdir" -type f -exec echo . \;

对于在 下找到的每个常规文件,这会在空行上输出一个点$subdir。我们输出一个点,因为这些很容易计数(文件名可以包含换行符)。

这些点通过管道传输到

printf '%s: %d\n' "${subdir%/}" "$( wc -l )"

这里,printf用于格式化输出。它采用子目录路径(删除最后一个斜杠)和文件数。

文件的计数wc -l将计算来自管道的点find(严格来说,它不计算点,而是计算换行符)。由于printf它本身不读取其标准输入流,因此它被 消耗wc -l

在开始时设置nullglobdotglobshell 选项允许我们在没有子目录$topdir(即 with )的情况下跳过整个循环,并且还可以在(即 with )nullglob下包含隐藏目录名称。$topdirdotglob

通过改变

topdir='./Dir1'

进入

topdir=$1

您可以让脚本将目录路径作为其唯一的命令行参数。

您可以find通过将其更改为稍微复杂一些来从根本上加快速度

find "$subdir" -type f -exec sh -c 'for pathname do echo .; done' sh {} +

(循环的其余部分应保持原样)。这会为批量找到的文件运行一个非常小的内联 shell 脚本,而不是echo为每个文件运行。这将是很多更快地假设echo是 shell 中的内置命令sh。 (您可能需要更改sh -cbash -c以确保这一点。)-exec echo . \;使用时,find将执行/bin/echo,这对于每个文件来说执行起来会很慢。

答案2

使用 GNU 实用程序:

find Dir1 -mindepth 2 -type f -printf '%P\0' |
  awk -F/ -vRS='\0' '{n[$1]++}; END{for (i in n) print i ": " n[i]}'

仅计数常规的的每个子目录的文件Dir1

输出类似:

sub1: 3
sub2: 30
sub3: 13
sub4: 3
sub5: 3

答案3

我不熟悉 Windows 上的 Gitbash,但我假设无论您在什么平台上运行此脚本,您都安装了这些:

  • bashv4.x 或更高版本(macOS 用户需要通过安装更新​​版本自制或者其他的东西)
  • GNU——find真的,任何标准的 Unixfind都可以,只是 MS-DOS/Windows 版本不行(更像是grep

假设上述情况,这个脚本应该可以解决问题:

#!/bin/bash
# USAGE: count_files <dir> ...

declare -A filecount

# Tell bash to execute the last pipeline element in this shell, not a subshell
shopt -s lastpipe

# Run through all the user-supplied directories at one go
for d in "$@"; do
  find "$d" -type f | while read f; do
    [[ $f =~ ^(${d%%/}/[^/]+)/ ]] && (( filecount["${BASH_REMATCH[1]}"]++ ))
  done
done

# REPORT!
for k in "${!filecount[@]}"; do
  echo "$k: ${filecount[$k]}"
done

答案4

假设你的bash版本至少是 4.0,实际上你已经差不多了。

你可以使用 shell 选项让你的代码递归地计算文件数量globstar。来自man bash(1)

**如果设置,路径名扩展上下文中使用的模式将匹配所有文件以及零个或多个目录和子目录。如果模式后跟/,则仅目录和子目录匹配。

如果您想递归计算顶级目录中的所有文件(包括子目录):

shopt -s dotglob globstar
for dir in */; do
    all=( "$dir"/** )
    printf '%s\n' "$dir: ${#all[@]}"
done

正如您尝试的代码中一样,对于每个顶级目录,我们都使用路径名扩展的结果填充一个数组,然后显示其元素的数量。
dotglob用于包含名称以.(隐藏文件)开头的文件。

如果要递归计算除子目录对象之外的所有文件,只需从所有文件的计数中减去子目录的计数即可:

shopt -s dotglob globstar
for dir in */; do
    all=( "$dir"/** )
    alldir=( "$dir"/**/ )
    printf '%s\n' "$dir: $(( ${#all[@]} - ${#alldir[@]} ))"
done

然而,在这里我假设“文件”的广泛定义,其中,在 POSIX 中,可以指常规文件、字符、块或 FIFO 特殊文件、符号链接、套接字、目录或任何可能超出标准的特定实现。
要仅计算特定类型的文件(例如常规文件),采用基于 的解决方案可能更容易find
或者,您可以扩展上面的代码,在循环中测试文件类型:

shopt -s dotglob globstar
for dir in */; do
    all=( "$dir"/** )
    count=0
    for file in "${all[@]}"; do
        test -f "$file" && count="$(( "$count" + 1 ))"
    done
    printf '%s\n' "$dir: $count"
done

但这种不太方便的解决方案也会比find基于 - 的替代方案慢得多(例如,比更快的方案慢两倍多)拘萨罗南达的回答bash,在 Linux 5.0 和4.6上进行了测试find)。

另请注意,find与默认行为不同,使用该globstar选项的路径名扩展将遵循解析为文件的符号链接,从而使上述所有片段也将它们包含在计数中。
(最初它也使用解析为目录的符号链接,但这种行为在bash4.3 中已更改)。

最后,为了还提供一个不依赖于globstarshell 选项的解决方案,您可以使用递归函数来递归地计算目录顶级子目录中的所有常规文件$1

#!/bin/bash

# nullglob is needed to avoid the function being
# invoked on 'dir/*' when * matches nothing
shopt -s nullglob dotglob

function count_files () {
    for file in "$1"/*; do
        # Only count regular files
        [ -f "$file" ] && count="$(( "$count" + 1 ))"
        # Only recurse on directories
        [ -d "$file" ] && count_files "$file"
    done
}

for dir in "$1"/*/; do
    count="0"
    count_files "$dir"
    printf '%s: %s\n' "$dir" "$count"
done

相关内容