이것은 유닉스 SE에 대한 여러 가지 질문에서 논의되었으며, 여기서 내가 할 수있는 모든 문제를 수집하려고 노력할 것입니다. 끝에 참조.
왜 실패
이러한 문제에 직면하는 이유는 단어 분할 이며 변수에서 확장 된 따옴표는 따옴표로 작용하지 않고 단지 평범한 문자 일뿐입니다.
질문에 제시된 사례 :
$ abc='ls -l "/tmp/test/my dir"'
여기서는 $abc
분할 ls
되어 두 개의 인수 "/tmp/test/my
와 dir"
첫 번째 앞면과 두 번째 뒷면의 따옴표를 가져옵니다 .
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
여기에서는 확장이 인용되어 있으므로 한 단어로 유지됩니다. 쉘은 ls -l "/tmp/test/my dir"
, 공백 및 따옴표가 포함 된 프로그램을 찾으려고합니다 .
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
그리고 첫 번째 단어 또는 만 $abc
인수로 사용 -c
되므로 Bash ls
는 현재 디렉토리에서 실행됩니다 . 다른 말은 bash에 대한 인수 $0
이며 $1
, 등 을 채우는 데 사용됩니다 .
$ bash -c $abc
'my dir'
으로 bash -c "$abc"
하고 eval "$abc"
, 거기에 따옴표 작업을 수행 추가적인 쉘 처리 단계,하지만 모든 쉘 확장이 다시 처리되도록 당신은 매우 아니라면 그래서 실수로 사용자가 제공 한 데이터로부터 명령 확장을 실행의 위험이있다, 인용에주의하십시오.
더 나은 방법
명령을 저장하는 두 가지 더 좋은 방법은 a) 대신 함수를 사용하고 b) 배열 변수 (또는 위치 매개 변수)를 사용하는 것입니다.
기능 사용하기 :
내부에 명령이있는 함수를 선언하고 마치 명령 인 것처럼 함수를 실행하십시오. 함수 내 명령의 확장은 명령이 정의 된 경우가 아니라 명령이 실행될 때만 처리되며 개별 명령을 인용 할 필요가 없습니다.
# define it
myls() {
ls -l "/tmp/test/my dir"
}
# run it
myls
배열 사용하기 :
배열을 사용하면 개별 단어에 공백이 포함 된 다중 단어 변수를 만들 수 있습니다. 여기서 개별 단어는 별개의 배열 요소로 저장되며 "${array[@]}"
확장은 각 요소를 별도의 셸 단어로 확장합니다.
# define the array
mycmd=(ls -l "/tmp/test/my dir")
# run the command
"${mycmd[@]}"
구문은 약간 끔찍하지만 배열을 사용하면 명령 줄을 하나씩 만들 수도 있습니다. 예를 들면 다음과 같습니다.
mycmd=(ls) # initial command
if [ "$want_detail" = 1 ]; then
mycmd+=(-l) # optional flag
fi
mycmd+=("$targetdir") # the filename
"${mycmd[@]}"
또는 명령 행의 일부를 일정하게 유지하고 배열을 사용하여 그 일부, 옵션 또는 파일 이름 만 채 웁니다.
options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir
transmutate "${options[@]}" "${files[@]}" "$target"
배열의 단점은 표준 기능이 아니기 때문에 일반 POSIX 셸 (예 dash
: /bin/sh
데비안 / 우분투 의 기본값 )은 지원하지 않습니다 (아래 참조). 그러나 Bash, ksh 및 zsh는 시스템에 배열을 지원하는 쉘이있을 수 있습니다.
사용 "$@"
명명 된 배열을 지원하지 않는 쉘에서, 여전히 위치 매개 변수 (의사 배열 "$@"
)를 사용하여 명령의 인수를 보유 할 수 있습니다.
다음은 이전 섹션의 코드 비트와 동등한 휴대용 스크립트 비트 여야합니다. 배열은 "$@"
위치 매개 변수 목록 인으로 대체됩니다 . 설정 "$@"
은로 이루어지며 set
큰 따옴표 "$@"
는 중요합니다 (이로 인해 목록의 요소가 개별적으로 인용됩니다).
먼저, 인수가있는 명령을 저장하고 실행하기 만하면 "$@"
됩니다.
set -- ls -l "/tmp/test/my dir"
"$@"
명령에 대한 명령 행 옵션의 일부를 조건부로 설정합니다.
set -- ls
if [ "$want_detail" = 1 ]; then
set -- "$@" -l
fi
set -- "$@" "$targetdir"
"$@"
"$@"
옵션 및 피연산자 에만 사용 :
set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir
transmutate "$@"
(물론, "$@"
일반적으로 스크립트 자체에 대한 인수로 채워져 있으므로 용도를 변경하기 전에 어딘가에 저장해야합니다 "$@"
.)
조심해 eval
!
으로 eval
인용 및 확장 처리 소개합니다 추가 수준, 당신은 사용자 입력에주의 할 필요가있다. 예를 들어, 사용자가 작은 따옴표를 입력하지 않는 한 작동합니다.
read -r filename
cmd="ls -l '$filename'"
eval "$cmd";
그러나 그들이 input을 제공하면 '$(uname)'.txt
스크립트는 행복하게 명령 대체를 실행합니다.
배열이있는 버전은 단어가 항상 분리되어 있기 때문에의 내용에 대한 인용이나 다른 처리가 없습니다 filename
.
read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"
참고 문헌