인용된 변수의 옵션은 실패하지만 인용되지 않은 변수는 작동하는 이유는 무엇입니까?

인용된 변수의 옵션은 실패하지만 인용되지 않은 변수는 작동하는 이유는 무엇입니까?

$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"

이것은 그렇지 않습니다($wget_options 주위의 큰따옴표에 주의):

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

  • 첫 번째 줄은 좋은 버전인가요? 아니면 이 동작을 일으키는 숨겨진 버그가 있다고 의심해야 합니까?

  • 일반적으로 bash와 해당 참조가 어떻게 작동하는지에 대한 좋은 문서는 어디에서 찾을 수 있나요? 이 스크립트를 작성하는 동안 나는 규칙을 이해하기보다는 시행착오를 바탕으로 작업을 시작하는 것처럼 느꼈습니다.

답변1

가장 안정적인 인코딩 방법은 배열을 사용하는 것입니다.

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

답변2

기본적으로 토큰화(및 파일 이름 생성)로부터 보호하려면 큰따옴표 변수 확장을 사용해야 합니다. 그러나 귀하의 예에서는

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분할이 필요합니다.

이를 수행하는 더 강력한 방법이 있습니다. do와 같은 배열을 사용하는 bash다른 쉘을 사용하는 경우 다음을 참조하십시오.bash글렌 잭맨의 답변.자일스의 대답표준 하우징과 같은 일반 하우징에 대한 대체 솔루션도 설명되어 있습니다 /bin/sh. 둘 다 기본적으로 각 옵션을 배열의 별도 요소로 저장합니다.

좋은 답변이 있는 관련 질문:공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?


큰따옴표 변수 확장은 좋은 경험 법칙입니다.. 그럼 주의하세요희귀한이렇게 하면 안되는 상황이 있습니다. 이는 위의 오류 메시지와 같은 진단 메시지를 통해 표시됩니다.

이렇게 하지 않는 상황도 있습니다필요참조 변수 확장. 그러나 어쨌든 별 차이가 없기 때문에 큰따옴표를 계속 사용하는 것이 더 쉽습니다. 그 사례 중 하나는

variable=$other_variable

다른 하나는

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

답변3

문자열 변수에 문자열 목록을 저장하려고 합니다. 맞지 않습니다. 변수에 어떻게 액세스하든 항상 무언가가 손상됩니다.

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

인용된 대체를 사용하여 변수에 액세스하면 wget "$wget_options"변수 값이 문자열로 사용됩니다. 즉, 단일 인수로 전달되므로 wget옵션입니다. 여러 옵션을 의미하기를 원하기 때문에 이는 귀하의 사례를 망칠 것입니다.

따옴표가 없는 대체를 사용하면 wget $wget_options문자열 변수의 값은 "split+glob"이라는 별명을 가진 확장 프로세스를 거칩니다.

  1. 변수의 값을 가져와 공백으로 구분된 부분으로 분할합니다(아직 변수를 수정하지 않았다고 가정 $IFS). 그러면 중간 문자열 목록이 생성됩니다.
  2. 중간 목록의 각 요소에 대해 하나 이상의 파일과 일치하는 와일드카드 패턴인 경우 해당 요소를 일치하는 파일 목록으로 바꿉니다.

분할 프로세스가 공백을 구분 기호로 변환하기 때문에 예제에서는 작동하지만 옵션에 공백과 와일드카드가 포함될 수 있기 때문에 일반적으로 작동하지 않습니다.

ksh, bash, yash 및 zsh에서는 배열 변수를 사용할 수 있습니다. 쉘 용어로 배열은 문자열 목록이므로 정보가 손실되지 않습니다. 배열 변수를 생성하려면 변수에 값을 할당할 때 배열 요소 주위에 괄호를 추가하세요. 배열의 모든 요소에 액세스하려면 배열 요소에서 목록을 형성하는 —를 일반화한 —를 사용합니다. 여기에도 큰따옴표가 필요합니다. 그렇지 않으면 모든 요소가 분할+글로브를 거치게 됩니다."${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 "$@" …

자세한 내용은 다음을 참조하세요.공백이나 기타 특수 문자 때문에 쉘 스크립트가 멈추는 이유는 무엇입니까?

관련 정보