32장. 몇 가지 지저분한 것들(Gotchas)

 

투란도트(Turandot): 수수께끼는 세 개, 그러나 죽음은 하나!

칼라프(Caleph): 아니오, 수수께끼는 세 개, 생명이 하나!

 푸치니(Puccini)

변수명에 예약어나 예약 문자를 할당하기.
case=value0       # 문제가 생깁니다.
23skidoo=value1   # 역시 문제가 생깁니다.
# 숫자로 시작하는 변수명은 쉘이 예약해 놓았습니다.
# 대신 _23skidoo=value1 를 쓰세요. 밑줄로 시작하는 변수는 괜찮습니다.

# 하지만...       밑줄로만 된 변수명은 제대로 동작하지 않습니다.
_=25
echo $_           # $_ 은 마지막 명령어의 마지막 인자로 세트되는 특수한 변수입니다.

xyz((!*=value2    # 심각한 문제가 생깁니다.

변수명에 하이픈이나 다른 예약 문자를 쓰기.
var-1=23
# 대신 'var_1' 를 쓰세요.

변수와 함수 이름을 똑같이 쓰기. 이렇게 하면 스크립트를 이해하기가 힘듭니다.
do_something ()
{
  echo "이 함수는 \"$1\" 로 뭔가를 합니다."
}

do_something=do_something

do_something do_something

# 문법적으로는 다 맞지만 엄청나게 헷갈리죠?

적절치 못한 곳에 공백문자를 쓰는 것(다른 프로그래밍 언어들과는 달리 bash는 공백문자에 대해 좀 까다로운 반응을 보입니다).
var1 = 23   # 'var1=23' 이 맞습니다.
# 이렇게 하면 bash는 "="과 "23" 인자를 갖는 "var1" 명령어를 실행시키려고 합니다.

let c = $a - $b   # 'let c=$a-$b' 나 'let "c = $a - $b"' 가 맞습니다.

if [ $a -le 5]    # if [ $a -le 5 ]   가 맞습니다.
# if [ "$a" -le 5 ]   라고 하면 더 좋겠죠.
# [[ $a -le 5 ]] 도 동작합니다.

초기화 안 된 변수(값이 할당되기 전의 변수)가 "0" 인 것으로 가정하기. 초기화 안 된 변수의 값은 0이 아니라 "널"(null)입니다.

테스트 문에서 =-eq 를 섞어 쓰기.= 은 문자를 비교하는 것이고 -eq 은 정수를 비교하는 것임을 명심하세요.
if [ "$a" = 273 ]      # $a 가 정수인가요? 문자열인가요?
if [ "$a" -eq 273 ]    # $a 가 정수라면.

# 때때로 반대의 결과없이 -eq 와 = 를 섞어 쓸 수 있습니다.
# 하지만...


a=273.0   # 정수가 아닙니다.

if [ "$a" = 273 ]
then
  echo "비교가 잘 됩니다."
else
  echo "비교가 잘 안 됩니다."
fi    # 비교가 잘 안 됩니다.

# a=" 273" 와 a="0273" 도 마찬가집니다.


# 비슷하게, 정수가 아닌 숫자에 "-eq"를 쓰는것도 문제를 일으킵니다.

if [ "$a" -eq 273.0 ]
then
  echo "a = $a'
fi  # 에러 메세지를 내고 abort 됩니다.
# test.sh: [: 273.0: integer expression expected

가끔은 "테스트" 대괄호([ ])안에서 쓰이는 변수를 큰 따옴표로 쿼우트 해줘야 할 필요가 있습니다. 이렇게 안 하면 예상치 못한 결과가 나올 수 있습니다. 예 7-5, 예 16-2, 예 9-5를 참고하세요.

스크립트의 소유자가 스크립트에서 쓰이는 명령어에 대한 실행 권한이 없어서 그 명령어를 실행시키지 못할 수도 있습니다. 명령어 줄에서 실행 시키지 못하는 명령어는 스크립트에서 실행시킨다 해도 역시 실패할 것입니다. 해결책은, 문제가 되는 명령어의 속성을 바꿔보는데, 정 안 되면 suid 비트를 설정해 보기 바랍니다.(당연히 루트로 해야겠죠).

- 를 재지향 연산자(실은 아니지만)처럼 쓰려고 하면 보통은 이상한 결과를 가져옵니다.
command1 2> - | command2  # command1의 에러 출력을 파이프
로 재지향 하려고 하지만...
#    ...제대로 동작하지 않을 겁니다.

command1 2>& - | command2  # 역시 실패합니다.

Thanks, S.C.

Bash 버전 2 이상에만 들어 있는 기능은 에러 메세지를 내면서 실패할 경우가 있습니다. 오래된 리눅스 머신의 경우에는 기본 설치시 Bash 버전 1.XX 대가 깔려 있을 수 있습니다.
#!/bin/bash

minimum_version=2
# Chet Ramey 가 Bash 에 기능을 계속 추가하고 있기 때문에
# $minimum_version 을 2.XX 나 적당한 버전으로 세트하면 됩니다.
E_BAD_VERSION=80

if [ "$BASH_VERSION" \< "$minimum_version" ]
then
  echo "이 스크립트는 Bash 버전 $minimum_version 이나 그 이상에서만 동작합니다."
  echo "Bash 를 업그레이드할 것을 강력히 추천합니다."
  exit $E_BAD_VERSION
fi

...

리눅스 머신이 아닌 곳의 본 쉘 스크립트(#!/bin/sh)에서 Bash 전용 기능을 쓰게 되면 예상치 못한 동작을 할 수 있습니다. 리눅스에서는 sh이 보통 bash를 나타내지만 일반적인 유닉스에서도 항상 이렇지는 않습니다.

도스 형태의 뉴라인(\r\n)으로 된 스크립트는 실행되지 않는데, #!/bin/bash\r\n이 우리가 원하는 #!/bin/bash\n과 같지 않아서 인식할 수 없기 때문입니다. 이럴 때는 그 스크립트가 유닉스 식의 뉴라인을 갖도록 변환해 줘야 합니다.

스크립트 헤더가 #!/bin/sh이라고 되어 있으면 완전한 Bash 호환 모드로 동작하지 않을 수 있습니다. 몇몇 Bash 전용 기능들을 못 쓸 수도 있습니다. 완전한 Bash 전용 확장 기능을 쓰고 싶은 스크립트는 헤더를 #!/bin/bash라고 해 주면 됩니다.

스크립트는 변수를 자기 부모 프로세스나 쉘, 환경으로 export 할 수 없습니다. 우리가 생물학 시간에 배웠듯이 자식은 부모에게서 물려 받지만 그 반대는 안 됩니다.
WHATEVER=/home/bozo
export WHATEVER
exit 0
bash$ echo $WHATEVER

bash$ 
역시, 명령어 프롬프트로 돌아가서 보면 $WHATEVER는 세트되어 있지 않습니다.

서브쉘 안에서 변수를 세트하고 조작한 다음 서브쉘 바깥에서 똑같은 변수를 사용하려고 한다면 별로 유쾌하지 않은 결과를 가져 옵니다.

예 32-1. 서브쉘 함정(Subshell Pitfalls)

#!/bin/bash
# 서브쉘 변수의 함정.

outer_variable=outer
echo
echo "outer_variable = $outer_variable"
echo

(
# 서브쉘 시작

echo "서브쉘 안에서의 outer_variable = $outer_variable"
inner_variable=inner  # 셋
echo "서브쉘 안에서의 inner_variable = $inner_variable"
outer_variable=inner  # 변수값 변경이 전역적으로 영향을 받을까요?
echo "서브쉘 안에서의 outer_variable = $outer_variable"

# 서브쉘 끝
)

echo
echo "서브쉘 밖에서의 inner_variable = $inner_variable"  # 언셋.
echo "서브쉘 밖에서의 outer_variable = $outer_variable"  # 안 변했죠.
echo

exit 0

스크립트 안에서 "suid" 명령어를 쓰면 시스템 보안을 위협할 수 있습니다. [1]

쉘 스크립트를 CGI 프로그래밍에 쓰는 것은 문제가 있습니다. 쉘 스크립트 변수는 "타입 세이프"(typesafe)하지 않기 때문에 CGI에 대해서만은 원하는 결과를 가져 오지 않을 수 있습니다. 게다가 "크래커 대해서 안전한"(cracker-proof) 쉘 스크립트를 짜는 것은 굉장히 어렵습니다.

 

Danger is near thee --

Beware, beware, beware, beware.

Many brave hearts are asleep in the deep.

So beware --

Beware.

 A.J. Lamb and H.W. Petrie

주석

[1]

스크립트에 suid 소유권을 거는 것은 무의미 합니다.