Bash

針對匹配大括號 + glob 模式的文件執行多個命令而不重複它

  • December 20, 2018

我想對一組由大括號和全域模式匹配的文件執行一系列命令,而不是在整個地方複製粘貼模式。我一直試圖通過將模式放入變數中來做到這一點,但一直無法弄清楚如何使該變數像原始模式一樣工作。我怎麼能這樣做,或者以其他方式解決這個問題?

例如,如何針對與標量變數中定義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
[...]

使用數組,不要將文件名通配模式儲存在變數中(讓它們擴展為匹配的路徑名):

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[@]}"

除非該數組包含數百或數千個路徑名。

評估整個命令(不僅僅是參數)

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

我不喜歡這個,但它有效。鑑於我們正在嘗試擴展一個包含大括號和文件 glob 的模式,其中一個發生在變數擴展之前,另一個發生在變數擴展之後,可能沒有其他替代方法:手動將變數擴展為包含整個命令呼叫的字元串,並將該字元串用作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";

引用自:https://unix.stackexchange.com/questions/490000