:에 이어
쉘 명령어 치환에서 예기치 않은 동작
나는 거대한 인수 목록을 취할 수있는 명령을 가지고 있는데, 그중 일부는 합법적으로 공백을 포함 할 수 있습니다 (아마도 다른 것들)
나는 따옴표로 나를 위해 그러한 인수를 생성 할 수있는 스크립트를 작성했지만 출력을 복사하여 붙여 넣어야합니다.
./somecommand
<output on stdout with quoting>
./othercommand some_args <output from above>
나는 단순히 그렇게함으로써 이것을 간소화하려고 노력했다.
./othercommand $(./somecommand)
위의 질문에서 언급 된 예기치 않은 동작이 발생했습니다. 문제는- othercommand
일부 인수에 인용이 필요하고이를 변경할 수 없다는 점에서 명령 대체를 안정적으로 사용하여 인수를 생성 할 수 있습니까?
답변
나는 인용문과 함께 나를 위해 그러한 인수를 생성 할 수있는 스크립트를 작성했습니다.
쉘에 대한 출력이 올바르게 인용 되고 출력을 신뢰하면 실행할 수 있습니다 eval
.
배열을 지원하는 쉘이 있다고 가정하면, 인수를 저장하기 위해 하나를 사용하는 것이 가장 좋습니다.
경우 ./gen_args.sh
출력을 같은 생산 'foo bar' '*' asdf
, 우리는 실행할 수 eval "args=( $(./gen_args.sh) )"
라는 배열 채우는 args
결과와 함께합니다. 즉, 세 가지 요소가 될 것이다 foo bar
, *
, asdf
.
"${args[@]}"
평소처럼 배열 요소를 개별적으로 확장 할 수 있습니다 :
$ eval "args=( $(./gen_args.sh) )"
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:
( "${array[@]}"
따옴표는 수정되지 않은 별도의 인수로 모든 요소로 확장됩니다. 따옴표없이 배열 요소는 단어 분할의 대상이됩니다. 예를 들어 BashGuide 의 배열 페이지를 참조하십시오 .)
그러나 , eval
행복하게 때문에, 어떤 쉘 대체를 실행 $HOME
출력의 홈 디렉토리로 확장 것이고, 명령 치환 실제로 쉘 실행에서 명령을 실행하는 것입니다 eval
. 출력은 "$(date >&2)"
하나의 빈 배열 요소를 만들고 stdout에 현재 날짜를 인쇄합니다. gen_args.sh
네트워크를 통한 다른 호스트와 같이 신뢰할 수없는 소스에서 데이터를 가져 오는 경우 다른 사용자가 만든 파일 이름이 문제가됩니다. 출력에는 임의의 명령이 포함될 수 있습니다. ( get_args.sh
자체가 악의적 인 경우 아무 것도 출력 할 필요가 없으며 악의적 인 명령을 직접 실행할 수 있습니다.)
eval없이 구문 분석하기 어려운 쉘 인용의 대안은 스크립트 출력에서 다른 문자를 구분 기호로 사용하는 것입니다. 실제 주장에 필요하지 않은 것을 선택해야합니다.
를 선택 #
하고 스크립트 출력을 갖도록 합시다 foo bar#*#asdf
. 이제 인용되지 않은 명령 확장을 사용 하여 명령의 출력을 인수로 분할 할 수 있습니다 .
$ IFS='#' # split on '#' signs
$ set -f # disable globbing
$ args=( $( ./gen_args3.sh ) ) # assign the values to the array
$ for var in "${args[@]}"; do printf ":%s:\n" "$var"; done
:foo bar:
:*:
:asdf:
IFS
스크립트의 다른 부분에서 단어 분리에 의존하는 경우 나중에 다시 설정해야하며 ( unset IFS
기본값으로 설정해야 함) set +f
나중에 globbing을 사용하려는 경우 에도 사용해야 합니다.
Bash 또는 배열이있는 다른 쉘을 사용하지 않는 경우 해당 위치 매개 변수를 사용할 수 있습니다. 대신 교체 args=( $(...) )
하여 set -- $(./gen_args.sh)
사용 하십시오 . (여기에도 따옴표가 필요합니다 . 그렇지 않으면 위치 매개 변수에 단어 분할이 적용됩니다.)"$@"
"${args[@]}"
"$@"
답변
문제는 somecommand
스크립트가에 대한 옵션을 출력하면 옵션 othercommand
은 실제로 텍스트이며 쉘의 표준 구문 분석에 $IFS
달려 있습니다 (무슨 일이 일어나고 어떤 쉘 옵션이 영향을 받는지에 따라 영향을받습니다 . 일반적인 경우에는 그렇지 않습니다) 제어).
옵션 somecommand
을 출력 하는 데 사용 하는 대신을 사용하여 호출 하는 것이 더 쉽고 안전하며 강력합니다 othercommand
. somecommand
스크립트는 다음 될 것 래퍼 스크립트 의 주위에 othercommand
당신의 명령 행의 일부로서 어떤 특별한 방법으로 전화를해야하는 단점이 대신 도우미 스크립트의 일종 otherscript
. 래퍼 스크립트는 다른 옵션 세트로 다른 유사한 도구를 호출하는 도구를 제공하는 매우 일반적인 방법입니다 ( 실제로 쉘 스크립트 래퍼 file
에있는 명령을 확인하십시오 /usr/bin
).
에서 bash
, ksh
또는 zsh
, 당신은 쉽게 배열을 사용하는 래퍼 스크립트에 대한 개별 옵션 잡아 수 othercommand
있도록 등에 :
options=( "hi there" "nice weather" "here's a star" "*" )
options+=( "bonus bumblebee!" ) # add additional option
그런 다음 othercommand
래퍼 스크립트 내에서 호출하십시오 .
othercommand "${options[@]}"
의 확장은 배열 "${options[@]}"
의 각 요소 options
가 개별적으로 인용되고 othercommand
별도의 인수로 표시되도록 합니다.
래퍼의 사용자는 실제로 호출하고 있다는 사실을 알지 못합니다 . 스크립트가 대신 명령 행 옵션을 출력으로 생성 한 경우에는 사실 othercommand
이 아닙니다othercommand
.
에서을 /bin/sh
사용 $@
하여 옵션을 유지하십시오.
set -- "hi there" "nice weather" "here's a star" "*"
set -- "$@" "bonus bumblebee!" # add additional option
othercommand "$@"
( set
위치 파라미터를 설정하기 위해 사용되는 명령이다 $1
, $2
, $3
등이 어떻게 배열 구성하는있는 $@
초기 표준 POSIX 쉘을. --
에 신호를하는 것입니다 set
주어진 어떤 옵션 인수 만이 없는지. --
되어 정말 에만 경우 필요 첫 번째 값은 -
)로 시작하는 것 입니다.
큰 따옴표 $@
이며 ${options[@]}
요소가 개별적으로 단어 분리되지 않고 파일 이름이 붙지 않도록합니다.
답변
는 IF somecommand
출력이 안정적으로 좋은 쉘 구문에, 당신은 사용할 수 있습니다 eval
:
$ eval sh test.sh $(echo '"hello " "hi and bye"')
hello
hi and bye
그러나 출력에 유효한 인용이 있는지 확인해야합니다. 그렇지 않으면 스크립트 외부에서 명령을 실행할 수도 있습니다.
$ cat test.sh
for var in "$@"
do
echo "|$var|"
done
$ ls
bar baz test.sh
$ eval sh test.sh $(echo '"hello " "hi and bye"; echo rm *')
|hello |
|hi and bye|
rm bar baz test.sh
이 echo rm bar baz test.sh
때문에 스크립트로 전달되지 않았으며 ;
별도의 명령으로 실행되었습니다. 이것을 강조하기 위해 |
주변 $var
을 추가했습니다 .
일반적으로의 출력을 완전히 신뢰할 수 없으면 출력 somecommand
을 안정적으로 사용하여 명령 문자열을 작성할 수 없습니다.