$()의 명령으로 인쇄된 {1,2}가 보간되지 않는 이유는 무엇입니까?

$()의 명령으로 인쇄된 {1,2}가 보간되지 않는 이유는 무엇입니까?

나는 두 개의 텍스트 파일이 있는 디렉터리에 있습니다.

$ touch test1.txt
$ touch test2.txt

일부 패턴을 사용하여 (Bash를 사용하여) 파일을 나열하려고 하면 작동합니다.

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

그러나 에 포함된 명령으로 패턴이 생성되면 다음 $()패턴 중 하나만 작동합니다.

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

여기서 무슨 일이 일어나고 있는 걸까요? 패턴이 {1,2}작동하지 않는 이유는 무엇입니까?

답변1

그것은 두 가지의 조합입니다. 첫째, 중괄호 확장은 파일 이름과 일치하는 패턴이 아닙니다. 이는 순전히 텍스트 대체입니다.`a[bc]d`(괄호)와 `a{b,c}d`(중괄호)의 차이점은 무엇입니까?. 둘째, 큰따옴표( ) 외부에서 명령 대체 결과를 사용하는 경우 ls $(…)완전한 재분석이 아닌 패턴 일치(및 단어 분할: "split+glob" 연산자)만 발생합니다.

를 사용하면 ls $(echo 'test?.txt')명령이 echo 'test?.txt'문자열을 출력합니다 test?.txt(최종 개행 포함). 명령 대체로 인해 문자열이 생성됩니다 test?.txt(명령 대체가 후행 개행을 제거하므로 최종 개행 없음). 따옴표가 없는 이 대체는 단어 분할을 거쳐 공백 문자(보다 정확하게는 에 문자가 없음 ) test?.txt가 없기 때문에 단일 문자열로 구성된 목록을 생성합니다 . 이 단일 요소 목록의 각 요소는 조건부 와일드카드 확장을 거치며 문자열에 $IFS와일드카드 문자가 있으므로 와일드카드 확장이 발생합니다. ?패턴이 test?.txt하나 이상의 파일 이름과 일치하므로 목록 요소는 test?.txt패턴과 일치하는 파일 이름 목록으로 대체되어 test1.txt및 를 포함하는 두 요소 목록이 생성됩니다 test2.txt. 마지막으로 는 두 개의 인수 와 ls함께 호출됩니다 .test1test2

를 사용하면 ls $(echo 'test{1,2}')명령이 echo 'test{1,2}'문자열을 출력합니다 test{1,2}(최종 개행 포함). 명령 대체 결과는 문자열입니다 test{1,2}. 따옴표가 없는 이 대체는 단어 분할을 거쳐 단일 문자열로 구성된 목록을 생성합니다 test{1,2}. 이 단일 요소 목록의 각 요소는 조건부 와일드카드 확장을 거치는데, 이는 문자열에 와일드카드 문자가 없기 때문에 아무 작업도 수행하지 않습니다(요소는 그대로 유지됩니다). 따라서 ls단일 인수로 호출됩니다 test{1,2}.

비교를 위해 ls $(echo test{1,2}). 이 명령은 echo test{1,2}문자열을 출력합니다 test1 test2(최종 개행 포함). 명령 대체로 인해 문자열이 생성됩니다 test1 test2(최종 개행 없음). 따옴표가 없는 이 대체는 단어 분할을 거쳐 두 개의 문자열 test1과 을 생성합니다 test2. 그런 다음 문자열 중 어느 것도 와일드카드 문자를 포함하지 않으므로 단독으로 남겨 두 개의 인수 와 ls로 호출됩니다 .test1test2

답변2

확장 순서는 다음과 같습니다: 중괄호 확장; 물결표 확장, 매개변수 및 변수 확장, 산술 확장 및 명령 대체(왼쪽에서 오른쪽으로 수행됨) 단어 분리; 및 파일 이름 확장.

명령 대체 후에는 중괄호 확장이 발생하지 않습니다. eval을 사용하여 또 다른 확장을 강제할 수 있습니다.

eval echo $(echo '{1,2}lala')

그 결과는 다음과 같습니다

1lala 2lala

답변3

그 문제는 에 매우 특정한데 , 이는 파일 이름 확장(globbing)에서 중괄호 확장을 분리하고 다른 모든 확장보다 먼저 수행하기로 bash결정했기 때문입니다 .bash

맨페이지 에서 bash:

확장 순서는 다음과 같습니다: 중괄호 확장; 물결표 확장, 매개변수 및 변수 확장, 산술 확장 및 명령 대체(왼쪽에서 오른쪽으로 수행됨) 단어 분리; 및 경로 이름 확장.

귀하의 예에서는 너무 늦었을 때 bash명령 대체()를 수행한 후에만 중괄호가 표시됩니다 .$(echo ...)

이는 경로 이름 확장(globbing) 직전에(그리고 일부는 일부로서) 중괄호 확장을 수행하는 다른 모든 쉘과 다릅니다. 여기에는 csh버팀대 확장이 처음 발명된 곳이 포함되지만 이에 국한되지는 않습니다 .

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

후자의 예 는 csh, zsh, 또는 에서도 동일합니다 .ksh93mkshfish

또한 버팀대 확장에 주목하세요.글로빙의 일환으로glob(3)또한 라이브러리 기능(적어도 Linux 및 모든 BSD에서는)과 기타 독립적 구현(예: perl: )을 통해 사용할 수 있습니다 perl -le 'print join " ", <test{1,2}.txt>'.

왜 그것이 다르게 수행되었는지는 bash아마도 그 뒤에 숨겨진 이야기가 있을 것입니다. 그러나 FWIW 나는 어떤 논리적 설명도 찾을 수 없었고 모든 사후 합리화는 설득력이 없다고 생각합니다.

답변4

시도해 보세요:::

ls $(에코 테스트{1,2}\.txt)

백슬래시를 사용합니다. 이제 작동합니다. 또한 이전 포스터에서 말했듯이 따옴표를 제거하십시오. 도트는 패턴 일치를 위한 것이 아니라 문자 그대로 여기서는 마침표로 간주됩니다.

관련 정보