스크립트에 인수로 전달될 때 쉘 스크립트 출력이 잘못 분할됩니다.

스크립트에 인수로 전달될 때 쉘 스크립트 출력이 잘못 분할됩니다.

다음과 같은 두 개의 쉘 스크립트가 있다고 가정해 보겠습니다.

#!/bin/sh
#This script is named: args.sh

echo 1 "\"Two words\"" 3

, 그리고:

#!/bin/sh
#This script is named: test.sh

echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3

스크립트를 호출할 때:

sh test.sh $(sh args.sh)

,나는 받았다:

Argument 1: 1
Argument 2: "Two
Argument 3: words"

내가 얻을 것으로 예상되는 경우:

Argument 1: 1
Argument 2: Two words
Argument 3: 3

출력을 복사하여 sh args.sh입력으로 붙여넣는 것은 sh test.sh잘 작동하므로 실제로 쉘이 수행하는 작업은 아니라고 생각합니다. 을 호출하여 원하는/예상된 출력을 얻을 수 있습니다 sh args.sh | xargs sh test.sh.

그러나 첫 번째 스크립트(args.sh)의 출력을 xargs로 파이핑하지 않고 이 작업을 수행할 수 있는 동등한 방법이 있는지 궁금합니다. 스크립트를 원래 순서대로 호출하고 싶습니다. 매개변수 스크립트는 매개변수를 두 번째 스크립트로 출력합니다. 또한 이 호출이 예상대로 작동하지 않는 이유에 대한 설명을 찾고 있습니다.

답변1

문제의 일부는 args.sh에서 반환된 문자열이 직접 명령처럼 구문 분석되지 않고 $' \t\n'$IFS( ) 값에 의해서만 구문 분석된다는 것입니다. 다음을 사용하여 명령 추적을 켜보십시오 set -x.

$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh 1 '"Two' 'words"' 3
Argument 1: 1
Argument 2: "Two
Argument 3: words"
$

Single로 시작하는 줄에 주목하세요 +. 4개의 인수가 있으며 그 중 '"Two'2 'words"'개는 별도의 인수로 구문 분석됩니다. 당신이 원하는 것은 $IFS를 변경하는 것입니다.

$ set -x
$ IFS='"'
+ IFS='"'
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh '1 ' 'Two words' ' 3'
Argument 1: 1
Argument 2: Two words
Argument 3:  3
$

이는 모든 출력에 적용되지는 않습니다. 가장 좋은 방법은 args.sh의 출력을 변경하여 출력을 공백 이외의 쉼표나 콜론과 같은 문자로 구분하는 것입니다.

$ cat /tmp/args.sh
#!/bin/sh
#This script is named: args.sh

echo "1,Two words,3"
$ IFS=,
$ sh /tmp/test.sh $(sh /tmp/args.sh)
+ sh /tmp/args.sh
+ sh /tmp/test.sh 1 Two words 3
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$

답변2

변수 대체 $var또는 명령 대체를 $(cmd)따옴표로 묶지 않으면 결과는 다음과 같이 변환됩니다.

  1. 결과 문자열을 단어로 분할합니다. 공백(공백, 탭 및 줄바꿈의 순서)에서 분할이 발생합니다. 이는 설정을 통해 구성할 수 있습니다 IFS.1,2).
  2. 생성된 각 단어는 전역 패턴으로 처리되며 일부 파일과 일치하는 경우 해당 단어는 파일 이름 목록으로 대체됩니다.

결과는 문자열이 아니라 문자열 목록입니다. 또한 "따옴표 문자는 여기에 포함되지 않으며 문자열 확장의 일부가 아니라 셸 소스 구문의 일부입니다.

쉘 프로그래밍의 일반적인 규칙은 변수와 명령 대체를 제거해야 하는 이유를 알지 못하는 한 항상 큰따옴표를 두는 것입니다. 따라서 에서 test.shecho "Argument 1: $1"인수를 전달하려면 문제가 발생합니다. 에서 test.sh로 단어 목록을 전달해야 하지만 선택한 방법은 명령 대체를 포함하며 간단한 문자열에 대한 수단만 제공합니다.args.shtest.sh

IFS전달된 매개변수에 줄바꿈이 포함되지 않고 호출 프로시저의 약간의 변경이 허용될 수 있는 경우 줄바꿈만 포함하도록 설정할 수 있습니다 . args.sh한 줄에 하나의 파일 이름을 출력하고 복잡한 따옴표가 없는지 확인하십시오 .

IFS='
'
test.sh $(args.sh)
unset IFS

인수에 임의의 문자가 포함될 수 있는 경우(아마도 인수로 전달될 수 없는 null 바이트 제외) 일부 인코딩을 수행해야 합니다. 물론 모든 인코딩이 가능합니다. 이는 매개변수를 직접 전달하는 것과 동일하지 않습니다. 이는 불가능합니다. 예를 들어 ( 쉘이 지원하지 않는 경우 args.sh실제 탭으로 대체 ):\t

for x; do
  printf '%s_\n' "$x" |
  sed -e 's/q/qq/g' -e 's/ /qs/g' -e 's/\t/qt/g' -e 's/$/qn/'
done

그리고 test.sh:

for arg; do
  decoded=$(printf '%s\n' "$arg" |
            sed -e 's/qs/ /g' -e 's/qt/\t/g' -e 's/qn/\n/g' -e 's/qq/q/g')
  decoded=${decoded%_qn}
  # process "$decoded"
done

test.sh문자열 목록을 입력으로 허용하도록 변경하는 것을 선호할 수도 있습니다 . 문자열에 개행 문자가 포함되어 있지 args.sh | test.sh않으면 args.sh(설명하다):

while IFS= read -r line; do
  # process "$line"
done

참조가 전혀 필요하지 않은 또 다른 방법은 첫 번째 스크립트에서 두 번째 스크립트를 호출하는 것입니다.


args.sh "$@"

관련 정보