Skip to main content

8. Test

test는 거의 모든 셸 스크립트에서 사용됩니다. 테스트가 직접 호출되는 경우가 많지 않기 때문에 그렇게 보이지 않을 수도 있습니다. test는 []로 더 자주 호출됩니다. []는 셸 프로그램을 더 읽기 쉽게 만들기 위해 테스트에 대한 기호적 링크입니다. 또한 일반적으로 셸에 내장되어 있습니다(즉, Unix 환경이 다르게 설정되어 있더라도 셸 자체에서 [ 를 테스트의 의미로 해석합니다):

$ type [
[ is a shell builtin
$ which [
/usr/bin/[
$ ls -l /usr/bin/[
lrwxrwxrwx 1 root root 4 Mar 27 2000 /usr/bin/[ -> test

즉, '['는 실제로는 ls 및 다른 프로그램과 마찬가지로 프로그램이므로 공백으로 둘러싸여 있어야 합니다:

if [$foo = "bar" ]

는 작동하지 않으며, 시작 '['가 없는 ']'인 test$foo = "bar" ]처럼 해석됩니다. 모든 연산자 주위에 공백을 넣으세요. 필수 공백을 'SPACE'라는 단어로 강조 표시했는데, 공백이 없으면 작동하지 않으므로 'SPACE'를 실제 공백으로 바꾸세요:

if SPACE [ SPACE "$foo" SPACE = SPACE "bar" SPACE ]

참고: 일부 셸은 문자열 비교에 "=="도 허용하지만, 이는 이식성이 없으므로 문자열에는 단일 "="를 사용하거나 정수의 경우 "-eq"를 사용해야 합니다.

test는 간단하지만 강력한 비교 유틸리티입니다. 자세한 내용은 시스템에서 "man test"를 실행하세요. 하지만 여기서는 몇 가지 사용법과 일반적인 예제를 소개합니다.

test는 if 및 while 문을 통해 간접적으로 호출되는 경우가 가장 많습니다. test라는 프로그램을 만들어서 실행하려고 하면 프로그램 대신 이 셸 내장 함수가 호출되기 때문에 어려움을 겪게 되는 이유이기도 합니다! if...then...else...의 구문은 다음과 같습니다:

if [ ... ] then
  # if-code
else
  # else-code
fi

fi는 거꾸로 된 경우라는 점에 유의하세요! 나중에 대소문자 및 esac과 함께 다시 사용됩니다. 또한 구문에 유의하세요. "if [ ... ]"와 "then" 명령은 서로 다른 줄에 있어야 합니다. 또는 세미콜론 ";"으로 구분할 수도 있습니다:

if [ ... ]; then
  # do something
fi

다음과 같이 elif를 사용할 수도 있습니다:

if  [ something ]; then
 echo "Something"
 elif [ something_else ]; then
   echo "Something else"
 else
   echo "None of the above"
fi

[something ] 테스트가 성공하면 "Something"을 에코하고, 그렇지 않으면 [ something_else ]를 테스트합니다.
[something_else ]를 테스트하고 성공하면 “Something else"를 에코합니다. 다른 모든 테스트가 실패하면 "None of the above"를 에코합니다.

다음 코드 스니펫을 실행하기 전에 변수 X를 다양한 값으로 설정해 보세요(-1, 0, 1, hello, bye 등 시도해 보세요). 다음과 같이 하면 됩니다(변수 - 1부에서 변수를 내보내야 한다는 점을 지적해 주신 Dave에게 감사드립니다):

$ X=5
$ export X 
$ ./test.sh
  ... output of test.sh ... 
$ X=hello
$ ./test.sh
  ... output of test.sh ... 
$ X=test.sh
$ ./test.sh
  ... output of test.sh ...

그런 다음 $X를 기존 파일 이름(예: /etc/hosts)으로 사용하여 다시 시도합니다.

#!/bin/sh
if [ "$X" -lt "0" ]
then
  echo "X is less than zero"
fi
if [ "$X" -gt "0" ]; then
  echo "X is more than zero"
fi
[ "$X" -le "0" ] && \
      echo "X is less than or equal to  zero"
[ "$X" -ge "0" ] && \
      echo "X is more than or equal to zero"
[ "$X" = "0" ] && \
      echo "X is the string or number \"0\""
[ "$X" = "hello" ] && \
      echo "X matches the string \"hello\""
[ "$X" != "hello" ] && \
      echo "X is not the string \"hello\""
[ -n "$X" ] && \
      echo "X is of nonzero length"
[ -f "$X" ] && \
      echo "X is the path of a real file" || \
      echo "No such file: $X"
[ -x "$X" ] && \
      echo "X is the path of an executable file"
[ "$X" -nt "/etc/passwd" ] && \
echo "X is a file which is newer than /etc/passwd"

세미콜론(;)을 사용하여 두 줄을 연결할 수 있다는 점에 유의하세요. 이는 간단한 if 문에서 약간의 공간을 절약하기 위해 종종 수행됩니다. 백슬래시는 단순히 셸에 이것이 줄의 끝이 아니라 두 줄(또는 그 이상)을 하나로 취급해야 함을 알려줍니다. 이는 가독성을 높이는 데 유용합니다. 다음 줄은 들여쓰는 것이 일반적입니다.

이 예제에서 볼 수 있듯이 test는 숫자, 문자열 및 파일 이름에 대해 많은 테스트를 수행할 수 있습니다.

-a, -e(둘 다 "파일이 존재함"을 의미), -S(파일이 소켓), -nt(파일이 보다 최신 파일), -ot(파일이 보다 오래된 파일), -ef(경로가 동일한 파일을 참조) 및 -O(파일이 내 사용자 소유)는 기존 본 셸(예: Solaris, AIX, HPUX 등의 /bin/sh)에서 사용할 수 없다는 점을 지적해 주신 Aaron에게 감사드립니다.

if 문을 작성하는 더 간단한 방법이 있습니다: && 및 || 명령은 각각 결과가 참 또는 거짓일 때 실행할 코드를 지정합니다.

#!/bin/sh
[ $X -ne 0 ] && echo "X isn't zero" || echo "X is zero"
[ -f  $X ] && echo "X is a file" || echo "X is not a file"
[ -n  $X ] && echo "X is of non-zero length" || \
      echo "X is of zero length"

이 구문이 가능한 이유는 [ 라는 파일(또는 셸 빌트인)이 있기 때문이며, 이 파일은 test에 연결됩니다. 하지만 이 구문을 과도하게 사용하면 읽기 어려운 코드가 될 수 있으므로 주의하세요. if...then...else... 구조가 훨씬 더 가독성이 높습니다. 독자의 주의를 지나치게 분산시키지 않으려는 동안 루프와 사소한 상태 점검에는 [...] 구조를 사용하는 것이 좋습니다.

X를 숫자가 아닌 값으로 설정하면 처음 몇 번 비교하면 메시지가 표시됩니다:

test.sh: [: integer expression expected before -lt 
test.sh: [: integer expression expected before -gt 
test.sh: [: integer expression expected before -le
test.sh: [: integer expression expected before -ge

이는 -lt, -gt, -le 및 -ge 비교가 정수 전용으로 설계되어 문자열에서는 작동하지 않기 때문입니다. !=와 같은 문자열 비교는 "5"를 기꺼이 문자열로 취급하지만 "Hello"를 정수로 취급하는 합리적인 방법이 없으므로 정수 비교가 불만을 제기합니다. 셸 스크립트가 더 우아하게 작동하도록 하려면 테스트하기 전에 다음과 같이 변수의 내용을 확인해야 합니다:

echo -en "Please guess the magic number: " 
read X
echo $X | grep "[^0-9]" > /dev/null 2>&1 
if [ "$?" -eq "0" ]; then
  # If the grep found something other than 0-9
  # then it's not an integer.
  echo "Sorry, wanted a number"
else
  # The grep found only 0-9, so it's an integer.
  # We can safely do a test on it.
  if [ "$X" = "7" ]; then
    echo "You entered the magic number!"
  fi
fi

이렇게 하면 사용자에게 더 의미 있는 메시지를 전달하고 우아하게 종료할 수 있습니다. 변수에 대해서는 '변수 - 2부'(10장)에서 설명했으며, grep은 복잡한 변수이므로 여기서는 숫자(0~9)와 다른 문자가 포함된 텍스트 줄을 찾으므로 grep [^0~9]의 캐럿(^)은 숫자로만 구성되지 않은 줄만 찾습니다. 그러면 그 반대의 경우(성공이 아닌 실패에 따라 작동하는 경우)를 취할 수 있습니다. 알겠죠? >/dev/ null 2>&1은 모든 출력이나 오류를 사용자 화면으로 보내지 않고 특수한 "null" 장치로 보냅니다. 이 페이지에서는 grep -v [0-9]가 작동한다고 주장했지만 이는 너무 단순합니다.

다음과 같이 while 루프에서 test를 사용할 수 있습니다:

#!/bin/sh
X=0
while [ -n "$X" ]
do
  echo "Enter some text (RETURN to quit)"
  read X
  echo "You said: $X"
done

이 코드는 RETURN을 누를 때까지 계속 입력을 요청합니다(X는 길이가 0입니다). 스크립트가 작동하지 않는다고 지적해 주신 Justin Heath에게 감사드립니다. 그 동안 [ -n "$X" ]에서 $X 주위의 따옴표를 놓쳤기 때문입니다. 이 따옴표가 없으면 $X가 비어 있을 때 테스트할 것이 없습니다. 알렉산더 웨버는 이 스크립트를 실행하면 어수선하게 종료된다는 점을 지적했습니다:

$ ./test2.sh
Enter some text (RETURN to quit) 
fred
You said: fred
Enter some text (RETURN to quit) 
wilma
You said: wilma
Enter some text (RETURN to quit)

당신이 말했죠:

$

루프 내의 다른 테스트로 이 문제를 해결할 수 있습니다:

#!/bin/sh
X=0
while [ -n "$X" ]
do
  echo "Enter some text (RETURN to quit)"
  read X
  if [ -n "$X" ]; then
    echo "You said: $X"
  fi
done

이 페이지에서는 if 문에 대해 두 가지 다른 구문을 사용했다는 점도 참고하세요. 다음과 같습니다:

if [ "$X" -lt "0" ]
then
  echo "X is less than zero"
fi

.......... 그리고 ........

if [ ! -n "$X" ]; then
  echo "You said: $X"
fi

if 문과 then 구문 사이에는 반드시 공백이 있어야 합니다. 세미콜론이나 개행 중 어느 것이든 상관없지만 if 문과 then 문 사이에는 둘 중 하나가 반드시 있어야 합니다. 그냥 이렇게 말하는 것이 좋습니다:

if [ ! -n "$X" ]
  echo "You said: $X"

하지만 then과 fi는 반드시 필요합니다.