중복 없이 중괄호+글로브 패턴과 일치하는 파일에 대해 여러 명령을 실행합니다.

중복 없이 중괄호+글로브 패턴과 일치하는 파일에 대해 여러 명령을 실행합니다.

어디에서나 패턴을 복사하여 붙여넣는 대신 중괄호 및 와일드카드 패턴과 일치하는 파일 세트에 대해 일련의 명령을 실행하고 싶습니다. 패턴을 변수에 넣어서 이를 달성하려고 했지만 변수를 원래 패턴처럼 작동하게 만드는 방법을 알 수 없습니다. 어떻게 해야 하나요? 아니면 다른 방법으로 이 문제를 해결할 수 있나요?

예를 들어, 스칼라 변수에 정의된 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";

나는 이것을 좋아하지 않지만 작동합니다. 중괄호와 파일 전역이 포함된 패턴을 확장하려고 한다는 점을 고려하면(그 중 하나는 변수 확장 이전에 발생하고 다른 하나는 이후에 발생함) 아마도 다음과 같은 다른 옵션은 없을 것입니다. 전체 명령 호출 문자열을 저장하고 해당 문자열을 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";

관련 정보