我想对一组与大括号和通配符模式匹配的文件运行一系列命令,而不是将模式复制粘贴到各处。我一直试图通过将模式放入变量中来实现此目的,但无法弄清楚如何使该变量像原始模式一样工作。我怎样才能做到这一点,或者以其他方式解决这个问题?
例如,如何针对与标量变量中定义的cat
模式匹配的文件运行?src/component\ {a,b,c}/*.c
component_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";
我不喜欢这个,但它有效。鉴于我们正在尝试扩展一种包含大括号和文件全局的模式,其中一个发生在变量扩展之前,另一个发生在变量扩展之后,可能没有其他选择可以这样:手动将变量扩展为包含整个命令调用的字符串,并使用该字符串作为eval
or 的参数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";