어디에서나 패턴을 복사하여 붙여넣는 대신 중괄호 및 와일드카드 패턴과 일치하는 파일 세트에 대해 일련의 명령을 실행하고 싶습니다. 패턴을 변수에 넣어서 이를 달성하려고 했지만 변수를 원래 패턴처럼 작동하게 만드는 방법을 알 수 없습니다. 어떻게 해야 하나요? 아니면 다른 방법으로 이 문제를 해결할 수 있나요?
예를 들어, 스칼라 변수에 정의된 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
또는 의 매개변수 로 사용합니다 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";