如何仅在没有子目录的目录上运行 bash 脚本

如何仅在没有子目录的目录上运行 bash 脚本

我想处理父目录中的每个目录,但条件是这些目录中没有子目录。

现在我有以下目录结构:

Music
  Band_A
    Record_A1
    Record_A2
  Band_B
    Record_B1
      CD_1
      CD_2

以及以下脚本

while read -r dir
do
    echo "$dir"
     /Users/rusl/.cliapps/MQA_identifier "$dir" | tee "$dir"/mqa.log
done < <(find . -type d)

它所做的就是检查目录中的每个音乐文件是否使用 MQA 编码,并像这样记录其输出:

1   MQA 44.1K   10 - Bury A Friend [MQA 24_44].flac

它起作用并在 Record_A1 和 CD_1 等目录中创建我想要的日志。

但是它也创建了很多冗余文件。例如,它在 Band_A 目录中创建一个日志,其中包含 Band_A 目录中所有子目录中所有文件的输出,或者它在 Band_B 和 Record_B1 中创建一个日志,同样包含各自子目录中所有文件的输出。

那么我如何运行脚本并仅为没有嵌套目录的目录生成日志?

编辑:我还认为此脚本处理每个子目录的次数与嵌套在父级顶层目录中的次数相同。这并不是说它很重要,但仍然效率不高。

答案1

基本思想是遍历所有目录,测试是否有子目录,如果有,则运行脚本的一部分。

while read -r dir ; do
    subdircount=$(find "$dir" -maxdepth 1 -type d | wc -l)
    if [[ "$subdircount" -eq 1 ]] ; then
        echo "$dir"
        /Users/rusl/.cliapps/MQA_identifier "$dir" | tee "$dir"/mqa.log
    fi
done < <(find . -type d)

答案2

您可以使用以下命令查找叶目录:

find . -type d -links 2

-links 2选项查找具有 2 个硬链接的文件(在您的情况下为目录)。

目录具有:

  • 指向其自身的链接
  • 来自父目录的链接
  • 每个子目录的链接

因此,没有子目录的目录将有 2 个硬链接,这正是您想要的。

答案3

此命令

find . -name . -o -type d -print -prune

将生成非空输出当且仅当当前工作目录中至少有一个目录(核心思想来自这个答案)。您可以将其包含在 shell 代码中作为测试。例如

if ( cd -- "$dir" && [ -z "$(find . -name . -o -type d -print -prune)" ] ); then
fi

子 shell( )阻止cd更改主脚本的当前工作目录。您可以使用find "$dir" …without cd,但如果$dir扩展为以 开头的内容怎么办-双划线适用于cd,而不适用于find。好吧,你的 main find( 中的那个<())以 开头.,因此人们可能会认为它生成的所有路径名都以 开头.似乎并非所有的实现都是find这样的尽管如此,防御性编码仍然是好的。

或者,您可以将测试构建到主find命令中(内部<()):

find . -type d -exec sh -c '
   cd -- "$1" && [ -z "$(find . -name . -o -type d -print -prune)" ]
' find-sh {} \; -print

find-sh解释如下:中的第二个 sh 是什么sh -c 'some shell code' sh

请注意,此方法将sh在每个目录(有或没有子目录)中额外运行一次,这不是最优的。我将其作为向更大改进迈出的一小步来介绍,因为管道read不是最好的方法(如果必须使用,那么最好使用-r;但请考虑IFS=也)。

当像您那样通过管道传输到时findread包含换行符的名称将导致代码失败。如果可能,一个好的做法是从 内部运行所有内容find。就您而言,这似乎是可能的。以下代码是独立的(不在 内部<())。

find . -type d -exec sh -c '
   cd -- "$1" && [ -z "$(find . -name . -o -type d -print -prune)" ] && do_something_more_with "$1"
' find-sh {} \;

上面的代码仍然sh每个目录运行一次。现在可以通过以下方法改进:

find . -type d -exec sh -c '
   for dir do
      ( cd -- "$dir" && [ -z "$(find . -name . -o -type d -print -prune)" ] ) && do_something_more_with "$dir"
   done
' find-sh {} +

这里我使用了… && do_something_more_with "$dir",但你if … then …也可以选择。

在你的情况do_something_more_with "$dir"

{ echo "$dir"; /Users/rusl/.cliapps/MQA_identifier "$dir" | tee "$dir"/mqa.log; }

其中{ }组命令因此前面&&使得整个组有条件地运行。

或许而不是do_something_more_with "$dir"目录do_something_more_with .中的$dir。相关行将是:

( cd -- "$dir" && [ -z "$(find . -name . -o -type d -print -prune)" ] && do_something_more_with . )

但这是你的决定。你仍然可以echo "$dir"(或更好的printf "%s\n" "$dir";注意运行的整个 shell 代码find -exec都是单引号,所以不要输入printf '%s\n')。

如果某个目录中有非常多的子目录,我们确实不需要内部find列出它们。要判断输出是否为空,在第一行之后中断就足够了:

[ -z "$(find . -name . -o -type d -print -prune | head -n 1)" ]

head -c 1我们可以在第一个字符后中断,但我认为head -c这是不可移植的。)

因此优化后的代码可能如下所示:

find . -type d -exec sh -c '
   for dir do
      ( cd -- "$dir" && [ -z "$(find . -name . -o -type d -print -prune | head -n 1)" ] ) \
      && { printf "%s\n" "$dir"; /Users/rusl/.cliapps/MQA_identifier "$dir" | tee "$dir"/mqa.log; }
   done
' find-sh {} +

我还认为此脚本处理每个子目录的次数与嵌套在父级顶层目录中的次数相同。虽然这并不重要,但仍然效率不高。

无法重现。find . -type d只打印每个目录一次。可能MQA_identifier以递归方式工作。如果确实如此,那么它(但不严格剧本) 将多次处理子目录(tee每次写入目录树中不同级别的文件)。

相关内容