对匹配大括号+glob 模式的文件运行多个命令而不重复

对匹配大括号+glob 模式的文件运行多个命令而不重复

我想对一组与大括号和通配符模式匹配的文件运行一系列命令,而不是将模式复制粘贴到各处。我一直试图通过将模式放入变量中来实现此目的,但无法弄清楚如何使该变量像原始模式一样工作。我怎样才能做到这一点,或者以其他方式解决这个问题?

例如,如何针对与标量变量中定义的cat模式匹配的文件运行?src/component\ {a,b,c}/*.ccomponent_source_code

示例上下文和再现

set -euo pipefail;
mkdir "src/" "dist/";
trap 'rm -r "src/" "dist/"' EXIT;

我有一个项目,其结构类似于以下内容(尽管有更有用的内容)。

>"src/README.md"                 date;
mkdir "src/component a/";
>"src/component a/program.c"     date;
>"src/component a/tests.c"       date;
>"src/component a/budget\$.txt"  date;
mkdir "src/component b/";
>"src/component b/program.c"     date;
>"src/component b/tests.c"       date;
>"src/component b/braces{}.txt"  date;
mkdir "src/component c/";
>"src/component c/program.c"     date;
>"src/component c/tests.c"       date;
>"src/component c/test data.txt" date;
mkdir "src/docs";
>"src/docs/test data.txt"        date;

我有需要针对多个组件中的相关文件的构建步骤。我已经用大括号+glob 模式定义了变量来匹配此类文件集。

readonly component_paths_pattern="src/component\ {a,b,c}";
readonly component_data_pattern="${component_paths_pattern}/*.txt";
readonly component_code_pattern="${component_paths_pattern}/*.c";

当我手动将这些模式复制到示例命令中时,它们与预期的文件匹配。

>"dist/support.txt" cat src/component\ {a,b,c}/*.txt;
test -s "dist/all test data.txt";

>"dist/all.c" cat src/component\ {a,b,c}/*.c;
test -s "dist/all.c";

如果我只需引用它们一次就可以了,但实际上我需要从构建脚本的不同部分多次引用相同的文件集,因此我希望在变量中重用这些模式。但是,我一直无法弄清楚如何使其正常工作。

set -x;

尝试失败的解决方案

无引号变量扩展(拆分+通配符)

>"dist/support.txt" cat ${component_data_pattern};

我认为这会失败,因为该模式包含空格,因此它被分成两个单独的 glob 模式参数,这两个参数本身都不匹配任何内容。

+ cat 'src/component\' '{a,b,c}/*.txt'
cat: src/component\: No such file or directory
cat: {a,b,c}/*.txt: No such file or directory

引用变量扩展

>"dist/support.txt" cat "${component_data_pattern}";

我认为这会失败,因为大括号扩展发生在变量扩展之前,因此大括号没有机会在这里扩展。

+ cat 'src/component\ {a,b,c}/*.txt'
cat: src/component\ {a,b,c}/*.txt: No such file or directory

参数列表中的 Eval 和 Echo

>"dist/support.txt" cat $(eval "echo ${component_data_pattern}");

如果不引用子命令扩展,我认为这会失败,因为某些生成的路径包含空格,导致它们被分成单独的参数。

++ eval 'echo src/component\ {a,b,c}/*.txt'
+++ echo 'src/component a/budget$.txt' 'src/component b/braces{}.txt' 'src/component c/test data.txt'
+ cat src/component 'a/budget$.txt' src/component 'b/braces{}.txt' src/component c/test data.txt
cat: src/component: No such file or directory
[...]
>"dist/support.txt" cat "$(eval "echo ${component_data_pattern}")";

如果我引用子命令扩展,我认为它会失败,因为这会将所有路径连接到单个字符串中,从而产生很长的无效路径。

++ eval 'echo src/component\ {a,b,c}/*.txt'
+++ echo 'src/component a/budget$.txt' 'src/component b/braces{}.txt' 'src/component c/test data.txt'
+ cat 'src/component a/budget$.txt src/component b/braces{}.txt src/component c/test data.txt'
cat: src/component a/budget$.txt src/component b/braces{}.txt src/component c/test data.txt: No such file or directory

参数列表中的 Eval 和 Printf %q

由于类似的原因,使用printf '%q '而不是失败。echo

>"dist/support.txt" cat "$(eval "printf '%q ' ${component_data_pattern}")";
++ eval 'printf '\''%q '\'' src/component\ {a,b,c}/*.txt'
+++ printf '%q ' 'src/component a/budget$.txt' 'src/component b/braces{}.txt' 'src/component c/test data.txt'
+ cat 'src/component\ a/budget\$.txt src/component\ b/braces\{\}.txt src/component\ c/test\ data.txt '
cat: src/component\ a/budget\$.txt src/component\ b/braces\{\}.txt src/component\ c/test\ data.txt : No such file or directory
>"dist/support.txt" cat $(eval "printf '%q ' ${component_data_pattern}");
++ eval 'printf '\''%q '\'' src/component\ {a,b,c}/*.txt'
+++ printf '%q ' 'src/component a/budget$.txt' 'src/component b/braces{}.txt' 'src/component c/test data.txt'
+ cat 'src/component\' 'a/budget\$.txt' 'src/component\' 'b/braces\{\}.txt' 'src/component\' 'c/test\' data.txt
cat: src/component\: No such file or directory
[...]

答案1

使用数组,并且不要将文件名通配模式存储在变量中(让它们扩展为匹配的路径名):

component_dirs=( 'src/component '{a,b,c} )

component_data=()
component_code=()

for dir in "${component_dirs[@]}"; do
    component_data+=( "$dir"/*.txt )
    component_code+=( "$dir"/*.c   )
done

然后你可以这样做,例如

cat "${component_data[@]}"

除非该数组包含数百或数千个路径名。

答案2

评估整个命令(不仅仅是参数)

eval ">\"dist/support.txt\" cat ${component_data_pattern}";
test -s "dist/all.c";

我不喜欢这个,但它有效。鉴于我们正在尝试扩展一种包含大括号和文件全局的模式,其中一个发生在变量扩展之前,另一个发生在变量扩展之后,可能没有其他选择可以这样:手动将变量扩展为包含整个命令调用的字符串,并使用该字符串作为evalor 的参数bash -c。不要忘记使用 转义任何内部引号\"

在上面的例子中,没有其他参数。如果还有其他参数并且这些参数也使用某种替换,则需要对这些参数进行转义(使用\$\*\{\}),以便它们不会展开,直到命令最终被计算并且可以在上下文中解释它们。

readonly annoying_arg="$PWD/src/docs/test data.txt";
eval ">\"dist/support.txt\" cat ${component_data_pattern} \"\$annoying_arg\"";
test -s "dist/all.c";

相关内容