스크립트에 인수로 전달되는 동안 쉘 스크립트 출력이 잘못 분할됨

스크립트에 인수로 전달되는 동안 쉘 스크립트 출력이 잘못 분할됨

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

#!/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에서 반환된 문자열이 직접 명령과 동일하게 구문 분석되지 않고 $IFS( ) 값에 의해서만 구문 분석된다는 것입니다 $' \t\n'. 다음을 사용하여 명령 추적을 켜보십시오 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"
$

단일로 시작하는 줄을 확인하세요 +. 4개의 인수가 있으며, '"Two''words"'2개는 별도의 인수로 구문 분석됩니다. 당신이 원하는 것은 $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. 결과로 나오는 각 단어는 glob 패턴으로 처리되며, 일부 파일과 일치하면 해당 단어가 파일 이름 목록으로 대체됩니다.

결과는 문자열이 아니라 문자열 목록입니다. 또한 다음과 같은 인용 문자는 "여기에 포함되지 않습니다. 이는 문자열 확장이 아닌 쉘 소스 구문의 일부입니다.

쉘 프로그래밍의 일반적인 규칙은 변수 및 명령 대체를 생략해야 하는 이유를 알지 못하는 한 항상 큰따옴표를 두는 것입니다. 그래서 에 를 test.sh쓰세요 echo "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 "$@"

관련 정보