따옴표 붙은 변수의 옵션이 실패하는 이유는 무엇입니까?


18

나는 $ foo 대신 "$ foo"와 같이 bash에서 변수를 인용해야한다는 것을 읽었습니다. 그러나 스크립트를 작성하는 동안 따옴표없이 작동하지만 따옴표로는 작동하지 않는 경우를 발견했습니다.

wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line 
relative_path="$3" # /XXX received from command line

이것은 작동합니다 :

wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

이것은하지 않습니다 (큰 따옴표 aroung $ wget_options 참고).

wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
  • 그 이유는 무엇입니까?

  • 첫 번째 줄이 좋은 버전입니까? 아니면이 동작을 일으키는 어딘가에 숨겨진 오류가 있다고 생각해야합니까?

  • 일반적으로 bash와 따옴표가 어떻게 작동하는지 이해하기위한 좋은 문서는 어디에서 찾을 수 있습니까? 이 스크립트를 작성하는 동안 규칙을 이해하는 대신 시행 착오를 거듭하기 시작했습니다.


3
귀하의 질문은 여기에 대한 답변 : mywiki.wooledge.org/BashFAQ/050
글렌 잭맨

3
규칙 소스 ( bash manual)로 이동하십시오 . 섹션 3.5 "쉘 확장", 특히 단어 분리 및 파일 이름 확장에주의를 기울이십시오.이 두 가지 요소는 따옴표를 사용하여 제어하는 ​​것입니다.
glenn jackman 12


4
커맨드 라인 인수가 어떻게 낮은 수준에서 작동하는지 이해하는 것이 도움이된다고 생각합니다. 프로그램이 실행될 때, 문자 목록으로 인수를받습니다 (충분히 닫습니다). 각각의 내부 목록은 우리가 "인수"라고 부릅니다. 대부분의 프로그램은 인수 간의 논리적 분리에 의존합니다. 여기서, 하나의 인수로 wget무엇을 --mirror --no-host-directories의미 하는지 알지 못하지만 두 개의 인수 로 나눌 때 처리합니다 . 공백과 따옴표가 인수 벡터 내부에 있으면 특수하게 처리하는 프로그램은 거의 없습니다 . 문제는 bash다른 쉘은>
HTNW

2
> 인간이 사용합니다. 인수 사이의 경계를 수동으로 정의하는 것은 성가신 일이므로 쉘은 공백으로 분할되어 라인 (문자 목록)을 인수 벡터 (문자 목록)로 바꿉니다. 변수 확장은 첫 번째 확장 중 하나 bash이므로 $a내용을 직접 작성하는 것과 정확히 동일합니다. 이제 문제는 분명합니다.으로 a="-a -b"; cmd "$a"확장 cmd "-a -b"되지만 cmd그 의미를 모를 것입니다. cmd $a로 확장되며 cmd -a -b아마도 작동 합니다.
HTNW

답변:


28

기본적으로 변수 확장은 단어 분리 (및 파일 이름 생성)로부터 보호하기 위해 큰 따옴표로 묶어야합니다. 그러나 귀하의 예에서

wget_options='--mirror --no-host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

단어 분할 은 정확히 당신이 원하는 것 입니다.

으로 "$wget_options"(인용), wget단일 인수로 무엇을 해야할지하지 않습니다 --mirror --no-host-directories및 불평

wget: unknown option -- mirror --no-host-directories

의 경우 wget두 가지 옵션보고 --mirror--no-host-directories같은 별도의를, 단어 분할이 발생한다.

이를 수행하는보다 강력한 방법이 있습니다. 당신이 사용하는 경우 bash또는 사용 배열이 좋아하는 다른 쉘 bash수행을 참조 글렌 잭맨의 답변을 . Gilles의 답변 은 표준과 같은 일반 쉘을위한 대체 솔루션을 추가로 설명합니다 /bin/sh. 둘 다 본질적으로 각 옵션을 배열의 별도 요소로 저장합니다.

좋은 답변과 관련된 질문 : 왜 쉘 스크립트가 공백이나 다른 특수 문자에서 질식합니까?


큰 따옴표로 묶는 변수 확장은 좋은 경험 법칙입니다. 그렇게 하세요. 그런 다음 그렇게하지 말아야 할 몇 가지 경우에주의하십시오. 위의 오류 메시지와 같은 진단 메시지를 통해 표시됩니다.

변수 확장을 인용 할 필요 가없는 경우도 있습니다 . 그러나 큰 차이는 없지만 큰 따옴표를 계속 사용하는 것이 더 쉽습니다. 그러한 사례 중 하나는

variable=$other_variable

다른 하나는

case $variable in
    ...) ... ;;
esac

2
split + glob 연산자를 사용하기 전에 $IFS올바른 값 이 포함되어 있는지 확인해야합니다 . 여기서 공백으로 분할해야하며 텍스트에 탭이나 줄 바꿈이 포함되어 있지 않으므로 기본값 $IFS은 그렇게하지만 코드가 $IFS수정 될 수 있는 컨텍스트에서 호출 될 수있는 함수에 사용되는 경우 , $IFS사전 에 설정하고 싶을 것입니다 (나머지 코드가 수정되지 않은 것으로 가정하면 나중에 복원하거나 로컬 범위를 사용하십시오 $IFS)
Stéphane Chazelas

32

배열을 사용하는 가장 강력한 코딩 방법 :

wget_options=(
    --mirror 
    --no-host-directories
    --directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"

이것이 정답입니다. 참조
l0b0

2
좋은 대답이므로, 나는 그것을 찬성했지만 Kusalanda는 내 코드가 왜 틀렸는 지 이해하고 더 하나만 받아 들일 수 있도록 도와주었습니다.
z32a7ul

rsync 목록에있는 누군가 가이 구성을 보여줄 때까지 나는 곤경에 빠졌습니다. 일부 요소가 빈 문자열 일 경우 특히 유용합니다. 빈 문자열이 사라집니다. 일부 명령은 명령이 다음과 같이 확장되면 예기치 않은 작업을 좋아 cp하고 rsync수행합니다 rsync '' rest of parameters. 이것은 조건부로 명령을 작성하고 한 곳에서 한 번만 실행하는 데 좋습니다.
Joe

17

문자열 목록에 문자열 목록을 저장하려고합니다. 맞지 않습니다. 변수에 어떻게 접근하든 무언가가 깨졌습니다.

wget_options='--mirror --no-host-directories'변수 wget_options를 공백이 포함 된 문자열로 설정합니다 . 이 시점에서 공백이 옵션의 일부인지 아니면 옵션 사이의 구분 기호인지를 알 수있는 방법이 없습니다.

따옴표로 묶은 변수로 변수에 액세스하면 변수 wget "$wget_options"값이 문자열로 사용됩니다. 즉,에 단일 매개 변수로 전달 wget되므로 단일 옵션입니다. 여러 옵션을 의미하기 때문에 귀하의 경우에는 문제가 발생합니다.

따옴표없는 대체를 사용하면 wget $wget_options문자열 변수 값에 "split + glob"라는 확장 프로세스가 적용됩니다.

  1. 변수 값을 가져 와서 공백으로 구분 된 부분으로 나눕니다 ( $IFS변수를 수정하지 않은 경우 ). 이로 인해 중간 문자열 목록이 생성됩니다.
  2. 중간 목록의 각 요소에 대해 하나 이상의 파일과 일치하는 와일드 카드 패턴 인 경우 해당 요소를 일치하는 파일 목록으로 바꾸십시오.

분할 프로세스에서 공백이 구분 기호로 바뀌기 때문에 옵션에서 공백과 와일드 카드 문자를 포함 할 수 있으므로 일반적으로 작동하지 않기 때문에이 예제에서는 작동합니다.

ksh, bash, yash 및 zsh에서는 배열 변수를 사용할 수 있습니다. 쉘 용어로 된 배열은 문자열 목록이므로 정보가 손실되지 않습니다. 배열 변수를 만들려면 값을 변수에 할당 할 때 배열 요소를 괄호로 묶습니다. 배열의 모든 요소에 액세스하려면 다음을 사용하십시오. 이것은 일반화입니다 . 여기에도 큰 따옴표가 필요합니다. 그렇지 않으면 각 요소에 split + glob이 적용됩니다."${VARIABLE[@]}""$@"

wget_options=(--mirror --no-host-directories --user-agent="I can haz spaces")
wget "${wget_options[@]}" 

일반 sh에는 배열 변수가 없습니다. 위치 인수를 잃어 버릴 염려가 없다면, 하나의 문자열 목록을 저장하는 데 사용할 수 있습니다.

set -- --mirror --no-host-directories --user-agent="I can haz spaces"
wget "$@" 

자세한 내용 은 쉘 스크립트가 공백 또는 기타 특수 문자에서 질식하는 이유는 무엇입니까?를 참조하십시오 .


일반 쉘에서는 서브 쉘이 위치 인수를 보존합니다 (set -- ...; exec wget "$@" ...).
John Kugelman은 Monica
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.