쉘 명령 대체의 예기치 않은 동작

쉘 명령 대체의 예기치 않은 동작

쉘 스크립트가 주어지면test.sh

for var in "$@"
do
    echo "$var"
done

그리고 전화해sh test.sh $(echo '"hello " "hi and bye"')

나는 출력을 기대했다

hello
hi and bye

하지만 다음을 얻으세요:

"hello
"
"hi
and
bye"

내가 다음으로 변경하면sh test.sh $(echo "hello " "hi and bye")

그러면 나는 얻는다

hello
hi
and
bye

이러한 동작 중 어느 것도 바람직하지 않습니다. 원하는 동작을 얻으려면 어떻게 해야 합니까?

내가 이해하고 싶은 것은 왜 동일하지 않습니까 sh test.sh $(echo "hello " "hi and bye")?sh test.sh "hello " "hi and bye"

답변1

명령 대체는 문자열을 생성합니다.

의 경우

$(echo '"hello " "hi and bye"')

문자열은 "hello " "hi and bye".

그런 다음 문자열이 토큰화됩니다(파일 이름 글로빙도 마찬가지이지만 이 예에는 영향을 미치지 않습니다). 토큰화는 문자 중 하나와 동일한 모든 문자 $IFS(기본적으로 공백, 탭 및 줄 바꿈)에서 발생합니다.

기본값은 IFS, "hello, ", "hi및 단어 and를 생성합니다 bye".

그런 다음 이를 스크립트에 별도의 매개변수로 제공하십시오.

두 번째 명령에서 명령 대체는 다음과 같습니다.

$(echo "hello " "hi and bye")

그러면 문자열이 생성되고 토큰화는 , , 및 hello hi and bye4개의 단어를 생성합니다 .hellohiandbye

마지막 예에서는매개변수를 사용 hello하고 hi and bye스크립트와 함께 직접 사용됩니다. 이것들에 익숙해단어 분할은 인용되기 때문에 발생합니다.

답변2

긴 이야기 짧게: 의 결과는 echo결과 분할의 희생자 $()이며 sh test.sh "hello " "hi and bye" 다른 규칙을 사용합니다.

set -x디버깅을 추가할 때 실제로 어떤 일이 발생하는지 살펴보겠습니다.

> set -x
> ./main.sh $(echo '"hello " "hi and bye"')
++ echo '"hello " "hi and bye"'
+ ./main.sh '"hello' '"' '"hi' and 'bye"'
"hello
"
"hi
and
bye"

echo"hello " "hi and bye"이는 명령 자체의 작은따옴표로 인해 단일 인수(예, 모든 공백과 작은따옴표 포함)로 수신됩니다. 그러나 명령 대체는 이를 "hello " "hi and bye".dereference 로 바꿉니다 .배쉬 매뉴얼:

큰따옴표 안에 대체가 발생하면 단어 분리 및 경로 이름 확장이 수행되지 않습니다.결과에 대하여.

그러나 귀하의 경우 따옴표 없이 명령을 대체하면 $IFS공백 문자(단어 분할을 위한 변수에 설정된 3개 문자 중 하나)를 기반으로 단어 분할이 허용됩니다. 따라서 결과 문자열을 모든 공백에서 끊으면 무엇을 얻게 될까요?

  1. "hello, 공백, 그 뒤에 다음 토큰이 있습니다.
  2. "- 양쪽에 공백으로 구분된 문자 자체입니다.
  3. "hi, 다시 왼쪽과 오른쪽에 공백이 있습니다.
  4. and
  5. bye"

전체적으로 공백으로 인해 손상된 5개의 토큰을 얻게 됩니다. 이 경우 분사는 정확히 명령 대체의 결과라는 점을 인식하는 것이 중요합니다. 그런 다음 이들 모두는 구문 분석된 개별 토큰으로 처리됩니다. set -x출력에서 볼 수 있듯이 쉘 스크립트는 이러한 태그를 사용하여 호출됩니다 .

성능이 다른 이유는 sh test.sh "hello " "hi and bye"적용되는 규칙이 다르기 때문입니다. 입력한 명령줄은 입력과 동시에 구문 분석되며 인용된 문자열은 단일 단위로 처리됩니다 hello.hi and bye

답변3

공백이 아닌 다른 것으로 변경할 수 있습니다 IFS( 의 공백을 보호하기 위해 hi and bye). 이를 사용하여 두 문자열을 구분합니다.

$ IFS=:
$ sh test.sh $(echo "hello ":"hi and bye")
hello 
hi and bye

(어차피 이 예제와 관련된) 작업 순서는 다음과 같습니다.

  1. 바꾸다
  2. 분사
  3. 견적 삭제

대체 결과로 생성된 따옴표는 토큰화 또는 ​​따옴표 제거 측면에서 특별하지 않으므로 (1) 출력의 따옴표는 다른 문자와 마찬가지로 (2)와 (3)을 통과합니다. 그래서:

  1. 에서 echo "hello " "hi and bye"쉘은 이러한 따옴표를 제거하여 echosum hellohi and bye출력을 얻고 hello hi and bye문자열을 공백으로 연결합니다.
  2. 에서 echo '"hello " "hi and bye"'쉘은 외부 것을 제거하고 'echo는 이를 가져 "hello " "hi and bye"와서 출력합니다.
  3. 명령 대체 에서는 로 sh test.sh $(echo '"hello " "hi and bye"')대체되지만"hello " "hi and bye"이것들따옴표는 대체의 결과이므로 단어 분리나 따옴표 제거가 필요하지 않습니다.

    따라서 쉘은 이를 "hello, ", "hi, and로 분할하여 bye"출력합니다.

  4. 에서는 sh test.sh $(echo "hello " "hi and bye")명령 대체가 로 대체되며 이는 , 및 hello hi and bye로 분할 됩니다 .hellohibye

답변4

어때요?

sh test.sh "hello " "hi and bye"

?

관련 정보