bash / POSIX 쉘에서 변수를 인용하는 것을 잊어 버린 보안 영향


206

unix.stackexchange.com을 오랫동안 따라왔다면 echo $varBourne / POSIX 쉘 (예 : zsh 예외)의 목록 컨텍스트에서 (와 같이 ) 인용되지 않은 변수를 남겨두면 매우 특별한 의미가 있음을 알고 있어야합니다. 그럴만한 이유가 없다면하지 말아야합니다.

여기에 여러 가지 Q & A가 자세히 설명되어 있습니다 (예 : 왜 쉘 스크립트가 공백이나 다른 특수 문자에서 질식합니까? , 큰 따옴표가 필요한시기는 언제입니까? , 셸 변수의 확장 및 glob 및 split의 영향 , 인용) 따옴표없는 문자열 확장)

70 년대 후반 Bourne 껍질이 처음 출시 된 이래로 Korn 껍질 ( David Korn의 가장 큰 후회 중 하나 (질문 # 7) )에 의해 변경되지 않았 거나 bash대부분 Korn 껍질을 복사 한 것입니다. POSIX / Unix에 의해 지정된 방법.

이제 우리는 여전히 여기에 많은 답변을 보았으며 때로는 변수가 인용되지 않은 쉘 코드를 공개적으로 공개했습니다. 사람들은 지금까지 배웠을 것이라고 생각했을 것입니다.

내 경험상 변수를 인용하지 않는 3 가지 유형의 사람들이 있습니다.

  • 초보자. 그것들은 완전히 직관적이지 않은 구문이라고 인정할 수 있습니다. 그들을 교육하는 것이이 사이트에서 우리의 역할입니다.

  • 건망증 사람들.

  • 반복적 인 망치질을 한 후에도 확신이없는 사람들은 반드시 Bourne 쉘 저자가 우리가 모든 변수를 인용 할 의도는 아니라고 생각합니다 .

우리가 이런 종류의 행동과 관련된 위험을 드러내면 설득 할 수있을 것입니다.

변수를 인용하는 것을 잊었을 때 발생할 수있는 것보다 최악의 상황은 무엇입니까? 그것은 정말 나쁜?

우리는 여기서 어떤 종류의 취약점에 대해 이야기하고 있습니까?

어떤 상황에서 문제가 될 수 있습니까?


8
BashPitfalls 는 내가 생각하는 것입니다.
pawel7318

에서 뒤로 내가 쓴이 글 작성자에 대한 감사
mirabilos

5
의 머리에 명중 한 사람 : 나는 4 그룹 추가 제안하고자하는 과도한 아마도 세 번째 그룹은 (피해자가 깡패가되는) 다른 사람들에게 자신의 좌절감을 복용의 회원들에 의해, 인용 너무 많은 시간을. 슬픈 것은 당연히 네 번째 그룹의 사람들이 가장 중요한 일을 인용하지 못할 수도 있다는 것입니다.
ack

답변:


201

전문

먼저 문제를 해결하는 올바른 방법이 아니라고 말하고 싶습니다. " 그렇지 않으면 감옥에 갈 것이기 때문에 사람들을 살해해서는 안됩니다 "라고 말하는 것과 비슷 합니다 .

마찬가지로 보안 취약점을 도입하고 있으므로 변수를 인용하지 않습니다. 변수가 잘못되어 있기 때문에 변수를 인용하십시오 (그러나 감옥에 대한 두려움이 도움이된다면 왜 그렇지 않을까요).

방금 기차에 뛰어든 사람들을위한 간단한 요약입니다.

대부분의 셸에서 변수 확장을 따옴표로 묶지 않은 상태로두면 (및이 답변의 나머지 부분이 명령 대체 ( `...`또는 $(...)) 및 산술 확장 ( $((...))또는 $[...]) 에도 적용됨 ) 매우 특별한 의미가 있습니다. 이를 설명하는 가장 좋은 방법은 일종의 암시 적 split + glob 연산자 ¹를 호출하는 것과 같습니다 .

cmd $var

다른 언어로 다음과 같이 작성됩니다.

cmd(glob(split($var)))

$var$IFS특수 매개 변수 ( 분할 부분)와 관련된 복잡한 규칙에 따라 단어 목록으로 먼저 분할 된 다음 해당 분할로 인한 각 단어 는 일치하는 파일 목록 ( 글로브 부분)으로 확장 되는 패턴으로 간주됩니다 .

경우 예를 들어, $var포함 *.txt,/var/*.xml$IFS 포함 ,, cmd인수의 수, 첫 번째 존재로 호출 될 수 cmd와있는 다음 사람 txt 은 현재 디렉토리에있는 파일과 xml의 파일을 /var.

cmd두 개의 문자 인수 cmd 와 로만 호출 *.txt,/var/*.xml하려면 다음 과 같이 작성하십시오.

cmd "$var"

다른 친숙한 언어로 된 것입니다.

cmd($var)

쉘의 취약점은 무엇을 의미 합니까?

결국, 쉘 스크립트는 보안에 민감한 컨텍스트에서 사용되어서는 안되는 것으로 알려져 있습니다. 분명히, 인용되지 않은 변수를 남겨 두는 것은 버그이지만 그렇게 많은 해를 끼칠 수는 없습니까?

글쎄, 누군가가 쉘 스크립트를 웹 CGI에 사용해서는 안된다는 사실이나 고맙게도 대부분의 시스템은 요즘 setuid / setgid 쉘 스크립트를 허용하지 않는다는 사실에도 불구하고 쉘 쇼크 (원격으로 악용 가능한 bash 버그) 2014년 9월)의 헤드 라인 공개 쉘은 여전히 광범위하게 사용되는 곳들은 아마 안 : CGI를에서, DHCP에서 클라이언트 후크 호출의 sudoers 명령에 스크립트 에 의해 (그렇지 않은 경우 )의 setuid 명령 ...

때때로 무의식적으로. 예를 들어 system('cmd $PATH_INFO') , php/ perl/ pythonCGI 스크립트에서 셸을 호출하여 해당 명령 줄을 해석합니다 ( cmd자체 스크립트는 쉘 스크립트 일 수 있으며 작성자는 CGI에서 호출 한 것으로 예상하지 않았을 수도 있음).

권한 에스컬레이션을위한 경로가있을 때, 즉 누군가 ( 공격 자라고 함 )가 의도하지 않은 일을 할 수있을 때 취약점 이 있습니다.

그것은 공격자가 데이터를 제공 한다는 것을 의미 하며 , 권한이없는 사용자 / 프로세스에 의해 데이터가 처리되고 있다는 것을 의미 합니다 .

기본적으로 버그가있는 코드 가 공격자 가 제어하는 ​​데이터를 처리 할 때 문제가 발생합니다 .

이제 데이터의 출처를 항상 명확하게 알 수있는 것은 아니며 코드에서 신뢰할 수없는 데이터를 처리 할 수 ​​있는지 여부를 판단하기가 어려운 경우가 많습니다.

변수에 관한 한 CGI 스크립트의 경우 데이터는 CGI GET / POST 매개 변수와 쿠키, 경로, 호스트 ... 매개 변수와 같은 것들입니다.

setuid 스크립트 (다른 ​​사용자가 호출 할 때 한 사용자로 실행)의 경우 이는 인수 또는 환경 변수입니다.

또 다른 매우 일반적인 벡터는 파일 이름입니다. 디렉토리에서 파일 목록을 얻는 경우 공격자 가 파일을 심었을 수 있습니다 .

(파일을 처리 할 때 그 점에서, 심지어 대화 형 쉘의 프롬프트에서, 당신은 취약 할 수 있습니다 /tmp또는 ~/tmp 예를 들어).

~/.bashrc예를 들어 클라이언트가 제어하는 ​​일부 변수가 있는 서버 배포 에서 유사 하게 실행되도록 bash호출 할 때 a 도 취약 할 수 있습니다 .sshForcedCommandgit

이제는 신뢰할 수없는 데이터를 처리하기 위해 스크립트를 직접 호출 할 수 없지만 다른 명령을 통해 호출 할 수 있습니다. 또는 잘못된 코드를 스크립트에 복사하여 붙여 넣을 수 있습니다 (3 년 동안 줄이나 동료 중 한 사람). 코드의 복사본이 어디에 있는지 알 수 없으므로 Q & A 사이트에서 특히 중요 합니다.

사업으로; 얼마나 나쁩니 까?

인용되지 않은 변수 (또는 명령 대체)를 남겨 두는 것은 쉘 코드와 관련된 보안 취약점의 가장 큰 원인입니다. 이러한 버그는 종종 취약점으로 번역되기도하지만 인용되지 않은 변수를 보는 것이 일반적이기 때문입니다.

실제로 쉘 코드에서 취약점을 찾을 때 가장 먼저 할 일은 인용되지 않은 변수를 찾는 것입니다. 공격자가 제어 할 수있는 데이터를 쉽게 추적 할 수 있습니다.

인용되지 않은 변수가 취약점으로 변할 수있는 방법은 무한합니다. 여기서 몇 가지 일반적인 추세를 설명하겠습니다.

정보 공개

대부분의 사람들은 분할 부분으로 인해 인용되지 않은 변수와 관련된 버그에 부딪 칠 것입니다 (예를 들어, 파일 이름은 요즘 이름에 공백이 있고 공백은 기본값 IFS입니다). 많은 사람들이 글로브 부분 을 간과합니다 . 글로브의 부분은 최소한으로 위험 분할 부분.

비위생 외부 입력에 대해 글 로빙을 수행 하면 공격자 가 모든 디렉토리의 내용을 읽을 수 있습니다.

에서:

echo You entered: $unsanitised_external_input

$unsanitised_external_input포함 된 경우 공격자 가의 콘텐츠를 볼 수 /*있음을 의미 합니다/ . 별거 아냐 그래도 더 흥미하게 함께 /home/*있는 시스템에 당신에게 사용자 이름의 목록을 제공 /tmp/*, /home/*/.forward다른 위험한 관행에서 힌트를 위해 /etc/rc*/*사용할 서비스 ... 필요가 개별적으로 이름을 없습니다. 값은 /* /*/* /*/*/*...전체 파일 시스템을 나열합니다.

서비스 거부 취약점.

이전 사례를 너무 멀리 가져 가면 DoS가 있습니다.

실제로, 입력되지 않은 입력을 가진 목록 컨텍스트에서 인용되지 않은 변수 는 적어도 DoS 취약점입니다.

전문가 쉘 스크립터조차도 일반적으로 다음과 같은 것을 인용하는 것을 잊습니다.

#! /bin/sh -
: ${QUERYSTRING=$1}

:no-op 명령입니다. 무엇이 잘못 될 수 있습니까?

그것은 설정되지 않은 경우 에 할당 $1하기 위한 것 입니다. 명령 줄에서 CGI 스크립트를 호출 할 수있는 빠른 방법입니다.$QUERYSTRING$QUERYSTRING

그것은 $QUERYSTRING여전히 확장되어 있고 인용되지 않았기 때문에 split + glob 연산자가 호출됩니다.

이제 확장하기 위해 특히 비싼 글로브가 있습니다. /*/*/*/*는 아래 4 단계까지 리스팅 디렉토리를 의미로 하나는 충분히 나쁘다. 디스크 및 CPU 활동 외에도 수만 개의 파일 경로 (여기서는 최소 서버 VM에 40k,이 중 10k는 디렉토리)를 저장해야합니다.

이제 /*/*/*/*/../../../../*/*/*/*40k x 10k를 의미 /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*하며 가장 강력한 기계조차 무릎을 꿇기에 충분합니다.

직접 해보십시오 (컴퓨터가 충돌하거나 멈출 수 있도록 준비하십시오).

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

물론 코드가 다음과 같은 경우 :

echo $QUERYSTRING > /some/file

그런 다음 디스크를 채울 수 있습니다.

쉘 cgi 또는 bash cgi 또는 ksh cgi 에서 Google 검색을 수행하면 에서 CGI를 작성하는 방법을 보여주는 몇 페이지가 있습니다. 프로세스 매개 변수의 절반이 얼마나 취약한 지 확인하십시오.

심지어 데이빗 콘의 고유 한 (쿠키 처리보고) 취약합니다.

임의의 코드 실행 취약성까지

공격자 가 어떤 명령을 실행할 수 있다면 , 그가 할 수있는 일에 제한이 없기 때문에 임의 코드 실행은 최악의 취약점 유형입니다 .

즉 일반적으로의 분할 에게 리드 부분. 이렇게 분할하면 하나의 인수 만 예상 될 때 여러 인수가 명령에 전달됩니다. 이들 중 첫 번째는 예상 된 컨텍스트에서 사용되지만 다른 것은 다른 컨텍스트에 있으므로 잠재적으로 다르게 해석됩니다. 예를 들어 더 나은 :

awk -v foo=$external_input '$2 == foo'

여기서는 $external_input쉘 변수 의 내용을 변수에 할당 하려고했습니다 foo awk.

지금:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

분할의 두 번째 단어 는 코드에 $external_input 할당되지 않고 코드로 foo간주됩니다 awk(여기서는 임의 명령을 실행 함 uname).

즉, 다른 명령을 실행할 수있는 명령 특히 문제의 ( awk, env, sed(GNU 일) perl, find...) 특히 (인수 후 옵션을 적용)는 GNU의 변종. 때로는 같은 다른 실행할 수 있도록 명령을 의심하지 않을 ksh, bash또는 zsh년대 [또는 printf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

라는 디렉토리를 만들면 x -o yes테스트하는 것과 완전히 다른 조건식이기 때문에 테스트가 긍정적이됩니다.

더 나쁜 것은, x -a a[0$(uname>&2)] -gt 1모든 ksh 구현을 가진 ( sh 최상의 상용 Unices와 일부 BSD 를 포함하여) 라는 파일을 만들면 uname 해당 쉘이 [명령 의 숫자 비교 연산자에 대한 산술 평가를 수행하기 때문에 실행 됩니다.

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

같은 bash파일 이름과 동일합니다 x -a -v a[0$(uname>&2)].

물론, 그들이 임의의 실행을 얻지 못하면 공격자 는 더 적은 피해를 입수 할 수 있습니다 (임의의 실행을 도울 수 있습니다). 파일을 쓰거나 권한, 소유권을 변경하거나 주요 또는 부작용을 가질 수있는 모든 명령을 악용 할 수 있습니다.

파일 이름으로 모든 종류의 작업을 수행 할 수 있습니다.

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

그리고 당신은 ..(재귀 적으로 GNU와 함께 chmod) 쓰기 가능 하게 만듭니다 .

공개적으로 쓰기 가능한 영역에서 파일을 자동으로 처리하는 스크립트 /tmp는 매우 신중하게 작성해야합니다.

는 어때 [ $# -gt 1 ]

그것은 내가 몹시 싫어하는 것입니다. 어떤 사람들은 따옴표를 생략 할 수 있는지 결정하기 위해 특정 확장이 문제가 될 수 있는지 궁금해하는 모든 어려움을 겪습니다.

말하는 것과 같습니다. 이봐, $#split + glob 연산자를 적용 할 수없는 것 같습니다 . shell에게 split + glob을 요청합시다 . 또는 버그가 발생하지 않기 때문에 잘못된 코드를 작성해 봅시다 .

지금은 얼마나 가능성이 적습니까? OK $#(또는 $!, $?또는 모든 산술 대체)에는 숫자 (또는 -일부) 만 포함될 수 있으므로 glob 부분이 빠져 있습니다. 를 들어 분할 부분은하지만 뭔가를 위해, 우리가 필요로하는 모든입니다 $IFS숫자를 포함 (거나 -).

일부 셸을 사용하면 $IFS환경에서 상속 될 수 있지만 환경이 안전하지 않은 경우 어쨌든 게임입니다.

이제 다음과 같은 함수를 작성하면

my_function() {
  [ $# -eq 2 ] || return
  ...
}

의미하는 것은 함수의 동작이 호출되는 컨텍스트에 달려 있다는 것입니다. 다시 말하면, $IFS 입력 중 하나가됩니다. 엄밀히 말하면 함수에 대한 API 문서를 작성할 때 다음과 같아야합니다.

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

그리고 함수를 호출하는 코드 $IFS는 숫자를 포함하지 않아야합니다. 이 두 개의 큰 따옴표 문자를 입력하고 싶지 않기 때문에 모든 것.

이제 해당 [ $# -eq 2 ]버그가 취약점 $IFS이 되려면 공격자가 제어 할 수 있는 가치가 있어야 합니다 . 공격자가 다른 버그를 악용 하지 않으면 일반적으로 발생하지 않습니다 .

그것은 들어 본 적이 없습니다. 사람들이 산술 표현에 데이터를 사용하기 전에 데이터를 삭제하는 것을 잊는 경우가 일반적입니다. 우리는 이미 일부 셸에서 임의의 코드를 실행할 수 있다는 것을 이미 보았지만, 모든 셸 에서 공격자 가 변수에 정수 값을 제공 할 수 있습니다 .

예를 들어 :

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

그리고와 $1(IFS=-1234567890), 산술 평가 설정 IFS의 부작용을 가지고, 다음 [ 명령에 대한 체크 의미하지 너무 많은 인수가 바이 패스된다.

어떤 경우에 대한 분할 + 글로브의 연산자가 호출되지 않는 이유는 무엇입니까?

변수 및 기타 확장에 따옴표가 필요한 또 다른 경우가 있습니다 : 패턴으로 사용될 때.

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

여부를 테스트하지 않습니다 $a$b(있는 경우를 제외하고 동일 zsh)하지만 경우 $a에 패턴과 일치 $b. 그리고 당신은 인용 할 필요 $b는 문자열로 비교하려는 경우 (같은 일에 "${a#$b}"또는 "${a%$b}"또는 "${a##*$b*}"어디 $b그렇지 않은 패턴으로 수행 될 경우 인용한다).

어떤 것을 의미하는 것은 즉 [[ $a = $b ]]여기서 경우에 true를 반환 할 수있다 $a상이하다 $b(예를 들어 때 $a이다 anything하고 $b있다 *) 또는 동일한 경우 (예컨대 둘 때 false를 반환 할 수있다 $a$b있다 [a]).

보안 취약점이 생길 수 있습니까? 예, 다른 버그와 마찬가지로 여기서 공격자 는 스크립트의 논리적 코드 흐름을 변경하거나 스크립트가 만들고있는 가정을 어길 수 있습니다. 예를 들어 다음과 같은 코드를 사용하십시오.

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

공격자 는를 통과하여 확인을 우회 할 수 있습니다 '[a]' '[a]'.

패턴 일치 나 split + glob 연산자를 모두 적용 하지 않으면 변수를 따옴표로 묶지 않는 위험은 무엇입니까?

나는 내가 쓰는 것을 인정해야한다.

a=$b
case $a in...

인용문은 해를 끼치 지 않지만 꼭 필요한 것은 아닙니다.

그러나 이러한 경우 (예 : Q & A 답변에서) 따옴표를 생략하면 부작용으로 초보자에게 잘못된 메시지를 보낼 수 있습니다 . 변수를 인용하지 않는 것이 좋습니다.

예를 들어, 그들은 a=$bOK이면 목록 컨텍스트에서 명령 에 대한 인수에있는 것처럼 많은 쉘에 있지는 않을export a=$b 것이라고 생각하기 시작할 수 있습니다 .exportenv a=$b

무엇에 대해 zsh?

zsh대부분의 디자인 어색함을 수정했습니다. 에서 zsh(적어도 SH / KSH 에뮬레이션 모드의 경우) 당신이 원하는 경우, 분할 , 또는 로빙 또는 패턴 매칭을 , 당신은 명시 적으로 요청해야 : $=var분할하고, $~varglob에 나 변수의 컨텐츠로 취급 패턴.

그러나 인용되지 않은 명령 대체시 (와 같이 echo $(cmd)) 여전히 분할 (글로브하지 않음)은 암시 적으로 수행됩니다 .

또한 변수를 인용하지 않는 경우에 따라 원하지 않는 부작용이 빈 제거 입니다. 이 zsh동작은 globbing을 모두 비활성화 set -f하고 (로 IFS='') 분리 () 하여 다른 쉘에서 수행 할 수있는 것과 유사합니다 . 아직도 안에:

cmd $var

아무 없습니다 분할 + 글로브 하지만, 경우 $var대신 하나 개의 빈 인수를 수신, 비어있는, cmd전혀 인수를받을 수 없습니다.

이로 인해 버그가 발생할 수 있습니다 (예 : 명백한 것 [ -n $var ]). 스크립트의 기대와 가정을 깨뜨려 취약점을 일으킬 수는 있지만 지금 당장 가져 오지 않은 예제를 만들 수는 없습니다.)

당신이 경우에 대해 필요가 분할 + 글로브 연산자를?

그렇습니다. 일반적으로 변수를 인용하지 않은 상태로 두려고합니다. 그러나 스플릿글로브 연산자를 사용하기 전에 올바르게 조정해야 합니다. glob 부분이 아닌 split 부분 만 원한다면 (대부분의 경우) globbing ( / ) 을 비활성화 하고 수정해야 합니다. 그렇지 않으면 위에서 언급 한 David Korn의 CGI 예제와 같이 취약점도 발생할 수 있습니다.set -o noglobset -f$IFS

결론

간단히 말해서, 쉘에 변수 (또는 명령 대체 또는 산술 확장)를 인용 부호로 남겨 두는 것은 특히 잘못된 컨텍스트에서 수행 될 때 실제로 매우 위험 할 수 있으며, 어떤 잘못된 컨텍스트인지 알기가 매우 어렵습니다.

그것이 나쁜 습관 으로 여겨지는 이유 중 하나입니다 .

지금까지 읽어 주셔서 감사합니다. 머리 위로 넘어가더라도 걱정하지 마십시오. 모든 사람이 코드를 작성하는 방식에 따른 코드 작성의 모든 의미를 이해할 것을 기대할 수는 없습니다. 그렇기 때문에 우리는 모범 사례 권장 사항 을 가지고 있으므로 이유를 이해하지 않고도 따를 수 있습니다.

(그리고 아직 명확하지 않은 경우, 쉘에 보안 관련 코드를 작성하지 마십시오).

그리고 이 사이트에 귀하의 답변에 변수를 인용하십시오!


¹In ksh93pdksh유도체는 중괄호 확장 로빙이 비활성화가 아니면 (경우에 수행되는 ksh93버전 짝수 때까지 + ksh93u braceexpand옵션을 사용할 수 없음).


와, 참고 [[: 비교 만 RHS 인용 할 필요가if [[ $1 = "$2" ]]; then
mirabilos

2
네, @mirabilos하지만, LHS는 필요가 없다 하지 아니 매력적인 이유는 우리가 할 수있는 의식적인 결정을 데리고 있다면 (거기를 인용하지가 없다, 그래서 인용 할 기본적으로 인용을 가 할 수있는 가장 현명한 일이 될 것 같은 ). 또한 첫 번째 문자 가 공백이 아닌 경우 [[ $* = "$var" ]]와 동일 하지 않습니다 (그리고이 경우 비어있는 경우 와 같은 것이 확실하지 않은 경우 버그를 제기해야합니까?). [[ "$*" = "$var" ]]$IFSbashmksh$IFS$*
Stéphane Chazelas

1
예, 기본적으로 인용 할 수 있습니다. 지금 필드 분할에 대한 버그를 더 이상 해결하지 마십시오. 다시 평가하기 전에 내가 아는 사람들을 먼저 수정해야합니다.
mirabilos

2
@Barmar, 당신이 의미하는 것은 foo='bar; rm *'아니지만, 정보 공개로 간주 될 수있는 현재 디렉토리의 내용을 나열합니다. print $foo에서 ksh93( 약점의 일부를 print대체하는 대체 echo) 코드 인 취약점 (예 :와 함께 foo='-f%.0d z[0$(uname>&2)]')이 있습니다 (실제로 필요 print -r -- "$foo"합니다 echo "$foo".
Stéphane Chazelas

3
나는 bash 전문가는 아니지만 10 년 이상 코딩했습니다. 따옴표를 많이 사용했지만 주로 포함 공백을 처리했습니다. 이제 더 많이 사용하겠습니다! 누군가 가이 대답을 확장하여 모든 좋은 점을 조금 더 쉽게 흡수 할 수 있다면 좋을 것입니다. 나는 그것을 많이 얻었지만 너무 많이보고 싶었습니다. 이미 긴 글이지만 여기에 배울 것이 더 많다는 것을 알고 있습니다. 감사!
Joe

34

[ 이 답변cas에서 영감을 얻었습니다 .]

그러나 만약 그렇다면 ...?

그러나 스크립트가 변수를 사용하기 전에 알려진 값으로 설정하면 어떻게됩니까? 특히 변수를 둘 이상의 가능한 값 중 하나로 설정 하지만 ( 항상 알려진 것으로 설정 한 값) 공백이나 글로브 문자를 포함하는 값이없는 경우에는 어떻게됩니까? 이 경우 따옴표없이 사용하는 것이 안전하지 않습니까?

가능한 값 중 하나가 빈 문자열이고 "빈 제거"에 의존하는 경우 어떻게해야합니까? 즉, 변수에 빈 문자열이 포함되어 있으면 명령에서 빈 문자열을 얻고 싶지 않습니다. 나는 아무것도 얻고 싶지 않다. 예를 들어

some_condition 인 경우
그때
    ignorecase = "-i"
그밖에
    ignorecase = ""
fi
                                        # 위의 명령에서 따옴표는 꼭 필요한 것은 아닙니다 . 
grep $ ignorecase   other_ grep _args

말할 수 없다 ; 빈 문자열 이면 실패 합니다.grep "$ignorecase" other_grep_args$ignorecase

응답:

다른 답변에서 논의했듯이 IFSa 또는가 포함되어 있으면 여전히 실패 -합니다 i. 당신이 보장 한 경우 IFS귀하의 변수에 모든 문자를 포함하지 않는 (당신이 당신의 변수가 어떤 글로브 문자를 포함하지 않는 것이 확실), 다음이 아마 안전하다.

그러나 더 안전한 방법이 있습니다 (약간 추악하고 직관적이지 않음). use ${ignorecase:+"$ignorecase"}. 에서 는 POSIX 쉘 명령 언어 사양 , 아래  2.6.2 매개 변수 확장 ,

${parameter:+[word]}

    대체 가치를 사용하십시오.   경우 parameter해제 또는 널, 널 치환한다; 그렇지 않으면 확장 word (또는 word생략 된 경우 빈 문자열 )을 대체해야합니다.

여기에 트릭은, 그것이 같은, 우리가 사용하고 있다는 점이다 ignorecase는 AS parameter"$ignorecase"는 AS word. 그래서 ${ignorecase:+"$ignorecase"}의미

경우 $ignorecase해제 또는 null (즉, 빈), null의 (즉, 인용 부호가 아무것도 ) 대체 할 수 없다; 그렇지 않으면 확장 "$ignorecase"이 대체된다.

이것은 우리가 가고 싶은 곳으로 우리를 데려갑니다 : 변수가 빈 문자열로 설정되면 변수는 "제거"됩니다 (이 전체의 복잡한 표현은 아무 것도 평가 되지 않습니다 -빈 문자열이 아닌 경우). 빈 값, 우리는 그 값을 인용합니다.


그러나 만약 그렇다면 ...?

그러나 단어로 나누고 싶은 변수가 있다면 어떻게해야합니까? (이것은 첫 번째 경우와 같습니다. 내 스크립트는 변수를 설정했으며 글로브 문자가 포함되어 있지 않다고 확신합니다. 그러나 공백이있을 수 있으며 공백에서 별도의 인수로 나누기를 원합니다. 경계.
난 아직도 원하는 PS는 제거를 비 웁니다.)

예를 들어

some_condition 인 경우
그때
    criteria = "-type f"
그밖에
    기준 = ""
fisome_other_condition 인 
경우
그때
    criteria = "$ criteria -mtime +42"
fi
"$ start_directory" 찾기 $ criteria   other_ find _args

응답:

이것이 사용하는 경우라고 생각할 수 있습니다 eval.  아니! eval여기서   사용하는 것에 대한 생각조차도 유혹에 저항 하십시오.

다시 말하지만 IFS변수에 문자가 포함되어 있지 않은지 확인하고 (공백을 제외하고 공백 제외) 변수에 glob 문자가 포함되어 있지 않다면 위의 내용은 아마도 안전한.

그러나 bash (또는 ksh, zsh 또는 yash)를 사용하는 경우 더 안전한 방법이 있습니다. 배열을 사용하십시오.

some_condition 인 경우
그때
    criteria = (-type f) #`criteria = ( "-type" "f")`라고 말할 수 있지만 실제로는 불필요합니다.
그밖에
    criteria = () # 이 명령에는 따옴표를 사용 하지 마십시오 !
fisome_other_condition 인 
경우
그때
    criteria + = (-mtime +42) # 참고 : 배열에 추가 (추가)하려면 `=`가 아니라` + =`입니다.
fi
"$의 start_directory" "$ 찾을 {기준을 [@]}"   other_ 찾을 _args을

에서 bash는 (1) ,

을 사용하여 배열의 모든 요소를 ​​참조 할 수 있습니다 . 경우 ... 입니다 거나  , 단어의 모든 구성원으로 확장 . 이 첨자는 단어가 큰 따옴표로 묶인 경우에만 다릅니다. 단어가 큰 따옴표로 묶인 경우…의 각 요소를 별도의 단어로 확장합니다 .${name[subscript]}subscript@*name${name[@]}name

따라서 "${criteria[@]}"위의 예제 criteria에서 각각 인용 된 배열 의 0, 2 또는 4 개의 요소로 확장됩니다 . 특히, 조건  s 중 어느 것도 참이 아닌 경우, criteria배열에는 내용이없고 ( criteria=()문에서 설정 한대로 ) 아무 것도"${criteria[@]}" 평가 하지 않습니다 (빈 문자열조차도 불편 함).


이것은 여러 단어를 다룰 때 특히 흥미롭고 복잡해집니다. 일부 단어는 동적 (사용자) 입력이며 사전에 알지 못하며 공백이나 기타 특수 문자가 포함될 수 있습니다. 치다:

printf "찾을 파일 이름을 입력하십시오 :"
fname을 읽으십시오
만약 [ "$ fname"! = ""]
그때
    기준 + = (-이름 "$ fname")
fi

참고 $fname인용 그것이 사용 시간을. 이것은 사용자가 foo bar또는 같은 것을 입력하더라도 작동합니다 foo*.  또는로 "${criteria[@]}"평가됩니다 . (배열의 각 요소는 인용되어 있음을 기억하십시오.)-name "foo bar"-name "foo*"

모든 POSIX 셸에서 배열이 작동하지는 않습니다. 배열은 ksh / bash / zsh / yash-ism입니다. 예외적으로… 모든 쉘이 지원하는 배열이 하나 있습니다 : 인수 목록 aka "$@". 호출 한 인수 목록을 다 사용한 경우 (예 : 모든 "위치 매개 변수"(인수)를 변수에 복사하거나 다른 방식으로 처리 한 경우) arg 목록을 배열로 사용할 수 있습니다.

some_condition 인 경우
그때
    set--type f #`set- "-type" "f"`라고 말할 수 있지만 실제로는 불필요합니다.
그밖에
    설정-
fisome_other_condition 인 
경우
그때
    set- "$ @"-mtime +42
fi
# 비슷하게 : set- "$ @"-name "$ fname"
"$의 start_directory" "$의 @"찾을   other_ 찾을 _args을

"$@"구조는 (이, 역사적으로, 처음 온)과 같은 의미를 가지고 - 각 인수를 확장 (즉, 인수 목록의 각 요소) 입력 한 것처럼 별도의 단어, ."${name[@]}""$1" "$2" "$3" …

에서 발췌 는 POSIX 쉘 명령 언어 사양 에 따라, 2.5.2 특수 매개 변수 ,

@

    1부터 시작하여 위치 매개 변수로 확장되어 설정된 각 위치 매개 변수에 대해 처음에 하나의 필드를 생성합니다. … 초기 필드는 별도의 필드로 유지되어야합니다.… 위치 매개 변수가 없다면, 큰 따옴표 안에 @있더라도 확장은 0 필드를 생성해야한다 @. …

전체 텍스트는 다소 비밀입니다. 요점은 "$@"위치 매개 변수가 없을 때 0 필드를 생성 하도록 지정한다는 것입니다. 역사적 메모 : "$@"1979 년 Bourne 쉘 (전임자)에서 처음 소개 "$@"되었을 때 위치 매개 변수가없는 경우 빈 문자열로 대체되는 버그가 있었습니다. 참조 무엇 않는 ${1+"$@"}쉘 스크립트에 의미와 어떻게 다릅니 까 "$@"? Traditional Bourne Shell Family무슨 ${1+"$@"}의미 입니까 ... 그리고 어디에서 필요한가요? "$@"${1+"$@"} .


배열은 첫 번째 상황에도 도움이됩니다.

some_condition 인 경우
그때
    ignorecase = (-i) #`ignorecase = ( "-i")`라고 말할 수 있지만 실제로는 불필요합니다.
그밖에
    ignorecase = () # 이 명령에는 따옴표를 사용 하지 마십시오 !
fi
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS (csh)

말할 것도없이 여기에 새로 온 사람들의 이익을 위해 : csh, tcsh 등은 Bourne / POSIX 쉘이 아닙니다. 그들은 완전히 다른 가족입니다. 다른 색의 말. 다른 모든 볼 게임. 고양이의 다른 품종. 또 다른 깃털의 새. 그리고 특히 다른 웜 캔이 있습니다.

이 페이지에 언급 된 내용 중 일부는 csh에 적용됩니다. 예를 들어, 정당한 이유가없는 한 모든 변수를 인용하는 것이 좋습니다. 그러나 csh에서 모든 변수는 배열입니다. 거의 모든 변수가 하나의 요소로 구성된 배열이므로 Bourne / POSIX 쉘의 일반 쉘 변수와 매우 유사하게 작동합니다. 그리고 구문은 끔찍하게 다릅니다 (그리고 나는 끔찍하게 의미합니다 ). csh-family 쉘에 대해서는 더 이상 언급하지 않습니다.


1
그것을 참고 csh하면 오히려 사용하는 것, $var:q보다 "$var"후자는 개행 문자가 포함 된 변수 작동하지 않는 (또는 개별적으로 배열의 요소를 인용보다는 하나의 인자에 공백으로 가입 할 경우).
Stéphane Chazelas

배쉬에서, bitrate="--bitrate 320"작동 ${bitrate:+$bitrate}bitrate=--bitrate 128작동하지 않습니다 ${bitrate:+"$bitrate"}는 명령을 나누기 때문이다. ${variable:+$variable}없이 사용 하는 것이 안전 "합니까?
Freedo

@Freedo : 귀하의 의견이 명확하지 않습니다. 내가 아직 말하지 않은 것을 알고 싶은 것이 확실하지 않습니다. 두 번째“하지만 만일의 경우”제목 —“그러나 단어로 나누고 싶은 변수가 있다면 어떻게해야합니까?”를 참조하십시오. 이것이 귀하의 상황이며, 조언을 따라야합니다. 그러나 당신이 할 수 없다면 (예를 들어, bash, ksh, zsh 또는 yash 이외의 쉘을 사용하고 다른 용도로 사용하고 있기 때문에  $@) 단순히 다른 이유로 배열 사용을 거부하는 경우 “다른 답변에서 논의 된대로” … (계속)
G-Man

(계속) ... 당신의 제안 (사용하지 ${variable:+$variable}아니하여이 ") IFS가 포함 된 경우 실패합니다  -,  0, 2, 3, a, b, e, i, r또는  t.
G-Man

글쎄, 내 변수는 a, e, b, i 2와 3 문자를 모두 가지고 있으며 여전히 잘 작동합니다.${bitrate:+$bitrate}
Freedo

11

나는 Stéphane의 대답에 회의적이지만 남용은 가능합니다 $#.

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

또는 $ ?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

이것들은 고안된 예이지만 잠재력이 존재합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.