我想对一组由大括号和全局模式匹配的文件运行一系列命令,而不是在整个地方复制粘贴模式。我一直试图通过将模式放入变量中来做到这一点,但一直无法弄清楚如何使该变量像原始模式一样工作。我怎么能这样做,或者以其他方式解决这个问题?
例如,如何针对与标量变量中定义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
[...]
评估整个命令(不仅仅是参数)
我不喜欢这个,但它有效。鉴于我们正在尝试扩展一个包含大括号和文件 glob 的模式,其中一个发生在变量扩展之前,另一个发生在变量扩展之后,可能没有其他选择可以这样:手动将变量扩展为包含整个命令调用的字符串,并将该字符串用作
eval
or的参数bash -c
。不要忘记使用 . 转义任何内部引号\"
。在上面的示例中,没有其他参数。如果还有其他参数并且它们也使用某种替换,则您需要转义这些参数(使用
\$
、\*
、\{
或\}
),以便在最终评估命令并且可以在上下文中解释它们之前不会扩展它们。使用数组,不要将文件名通配模式存储在变量中(让它们扩展为匹配的路径名):
然后你可以做,例如,
除非该数组包含数百或数千个路径名。