Test
Shell 에는 기본적으로 숫자와 스트링을 구분하는 데이터 타입이 없고 사칙연산을 위한 연산자도 없습니다. 명령문 상의 모든 문자는 스트링이며 산술연산은 별도의 확장 이나 명령 을 통해서 제공됩니다.
기본적으로 명령문에 사용되는 문자는 모두 스트링이다.
# 명령행의 인수를 스트링으로 받는 c 프로그램의 main 함수
int main(int argc, char **argv)
# 명령행의 인수를 스트링으로 받는 java 프로그램의 main 함수
public static void main(String[] args)
32
는 숫자인가 스트링인가? "32"
는 스트링인가 숫자인가? 일반적으로 프로그래밍 언어에서는 숫자와 스트링의 구분이 명확하여 혼용할 경우 오류가 발생합니다. 하지만 shell 에서는 데이터 타입이 존재하지 않기 때문에 32
, "32"
는 둘 다 같은 값입니다. 스트링을 다루는 곳에서 사용되면 스트링으로 사용되고 산술연산을 하는 곳에서 사용되면 숫자로 사용되는 식입니다.
# 숫자가 들어갈 자리에 "16" 을 스트링이 들어갈 자리에 16 을 넣어도된다
$ printf "decimal: %d, string: %s\n" "16" 16
decimal: 16, string: 16
# '+' 는 expr 명령에 전달되는 인수로 그냥 문자
$ expr "-16" + 10
-6
# '*' 는 glob 문자이므로 escape
$ echo "10" + 2 \* 5 | bc
20
# '-gt' 는 숫자를 다루는 연산자인데 "150" 과 비교해도 된다.
$ [ "150" -gt 25 ]; echo $?
0
# 두개의 피연산자 모두 quote 해도 상관없다.
$ [ "150" -gt "25" ]; echo $?
0
$ AA=123
$ case $AA in (123) echo Y ;; esac
Y
$ case $AA in ("123") echo Y ;; esac
Y
데이터에 타입이 없기 때문에 shell script 의 특징 중에 하나가 두 종류의 연산자를 제공한다는 것입니다. 하나는 숫자로 취급할때 사용되는 연산자 또 하나는 스트링으로 취급할때 사용되는 연산자입니다. 아래는 두값을 비교할때 사용되는 test
명령의 help
내용중 일부인데 두 종류의 연산자를 제공하는 것을 볼수있습니다.
String operators: # 스트링 으로 취급할때 사용
-z STRING True if string is empty.
-n STRING
STRING True if string is not empty.
STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True if STRING1 sorts after STRING2 lexicographically.
# 숫자로 취급할때 사용
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
[ ] , test
스크립트를 작성할때 거의 빠짐없이 등장하는 표현식이 테스트 표현식인데요. 이때 사용하는 명령이 test
, [
입니다. test
와 [
는 동일한 명령입니다. [
은 키워드같이 생겼지만 명령으로 사용법도 command arg1 arg2 ...
처럼 일반 명령과 같습니다. 따라서 위의 help 문서중에 나오는 연산자들은 [
명령의 인수이고 마지막에 붙이는 ]
도 인수가 됩니다.
# test 와 '[' 는 같은 명령이다.
$ test -d /home/user/foo; echo $?
1
$ [ -d /home/user/foo ]; echo $?
1
여담으로 프로그래머 중에는
[ ]
형식을 안 쓰고test
명령만 사용하는 사람도 있습니다.
null 이 아닌 값은 모두 true
[
명령에서 연산자를 사용하지 않을 경우 존재하지 않거나 null 값인 경우는 false 그외는 모두 true 에 해당합니다.
########## 거짓인 경우 ##########
$ [ ]; echo $?
1
$ [ "" ]; echo $?
1
$ [ $asdfgh ]; echo $? # 존재하지 않는 변수
1
$ AA=""
$ [ "$AA" ]; echo $? # null 값 변수
1
####### null 이 아닌 값은 모두 true 이다 #######
$ [ 0 ]; echo $?
0
$ [ 1 ]; echo $?
0
$ [ a ]; echo $?
0
$ [ -n ]; echo $?
0
$ AA=" "
$ [ "$AA" ]; echo $?
0
false 값이 0 ?
다음 예에서는 true 와 false 모두 테스트 결과가 0 이 나오고 있습니다. 프로그래밍 언어에서 true 와 false 는 키워드나 예약어로 자체 값을 갖지만 shell 에서는 builtin 명령입니다. 그래서 실행이 돼야 true 는 0 을 반환하고 false 는 1 을 반환합니다. 그런데 아래서는 [
명령의 인수로 사용되어 "false"
스트링과 같은 의미가 되었습니다.[
명령에서 null 이 아닌 스트링은 항상 true 입니다.
역사적으로 sh 에는 true, false 명령이 없었고 true 는
:
( colon 명령 ) 의 alias 였다고 합니다.
$ [ true ]; echo $?
0
$ [ false ]; echo $?
0
# 다음과 같이 if 문을 작성하면 true, false 명령이 실행됩니다.
$ isSet=true
$ if $isSet; then echo AAA; else echo BBB; fi // true 명령이 실행됨
AAA
$ cygwin=false msys=false darwin=false
$ if ! $cygwin && ! $msys && ! $darwin; then // false 명령이 세번 실행됨
echo BBB
fi
BBB
비교하는 변수는 quote 해야합니다.
[
명령은 인수로 사용하는 변수를 quote 하지 않으면 정상적인 결과가 나오지 않을 수 있습니다.
아래 첫번째 예는 값이 없는 변수 $AA 를 사용할때 quote 하지 않아서 결과적으로 [ -n $AA ]
명령은 [ -n ]
와 같게 되었습니다. 여기서 -n
는 스트링 "-n"
으로 해석되니까 항상 true 가 됩니다. 두번째는 quote 하였기 때문에 명령이 [ -n "" ]
와 같게 되어 정상적인 값이 나오게 됩니다.
$ AA="" # 변수 AA 값은 null
$ [ -n $AA ]; echo $? # null 이 아니여야 true 인데 결과로 true 가 나왔습니다.
0
$ [ -n "$AA" ]; echo $? # 변수를 quote 해주니 정상정인 값이 나왔습니다.
1
다음 예에서는 변수 AA 값이 null 이 아니기 때문에 echo 명령이 실행돼야 하지만 오류가 발생하였습니다. -n
연산자는 하나의 피연산자를 갖는데 $AA 를 quote 하지 않아서 결과적으로 [ -n aa bb ]
가되어 두개의 피연산자를 갖게 되었습니다.
$ AA="aa bb"
$ if [ -n $AA ]; then
echo "$AA"
fi
bash: [: too many arguments
# 다음 예도 마찬가지입니다.
$ AA="aa bb" BB="aa bb"
$ [ $AA = $BB ]; echo $?
bash: [: too many arguments
$ [ "$AA" = "$BB" ]; echo $?
0
위의 예를 통해서 알 수 있듯이 [
명령을 사용할 때는 항상 변수를 quote 해줘야 합니다.
숫자를 비교할때 스트링 연산자를 사용하면 안된다.
<
, >
연산자는 보통 프로그래밍 언어에서는 숫자를 비교할때 사용하지만 [ ]
, [[ ]]
에서는 그와 달리 스트링을 비교하는데 사용합니다. 위의 help 문서중에 나왔듯이 스트링을 비교할 때는 사전적으로 (lexicographically) 비교를 합니다. 그러므로 "100" 보다는 "2" 가 큰수가 됩니다. 왜냐하면 사전적으로 볼 때 처음 비교되는 문자가 "1" 보다 "2" 가 크기 때문입니다.
$ [ 100 \> 2 ]; echo $?
1
$ [ 100 -gt 2 ]; echo $?
0
AND , OR
[
명령에서는 and, or 연산자로 -a
, -o
를 제공하지만, &&
||
shell 메타문자를 이용해 분리해서 작성할 수도 있습니다.
두 연산자의 우선순위는 [
명령에서 제공하는 연산자일 경우 일반 프로그래밍 언어와 같이 -a
가 높고,
shell 메타문자를 이용할 경우는 두 메타문자의 우선순위를 같게 취급하므로 주의할 필요가 있습니다.
( 자세한 내용은 shell metacharacters precedence 참조 ).
# shell 메타문자를 이용해 분리해 사용
if [ A = A ] && [ B = B ]; then ...
if [ A = A ] || [ B = B ]; then ...
# { ;} 를 이용해 우선순위 조절
if [ A = A ] || { [ B = B ] && [ C = C ] ;} then ...
----------------------------------------------------
if [ A = A -a B = B ]; then ...
if [ A = A -o B = B ]; then ...
# 우선순위 조절을 위해 `( )` 를 사용할때는 shell 메타문자와 충돌하므로 escape 합니다.
if [ A = A -a \( B = B -o C = C \) ]; then ...
Logical NOT
다음과 같이 두가지 형태로 사용할 수 있습니다.
# test 명령에서 사용되는 '!' # shell logical NOT 키워드를 이용
if test ! A = A; then ... if ! test A = A; then ...
if [ ! A = A ]; then ... if ! [ A = A ]; then ...
Array 를 비교할때는 *
을 사용
Quote 을 하지 않을경우 ${array[@]}
와 ${array[*]}
는 차이가 없습니다. 어짜피 똑같이 IFS 값에 의해 단어 분리가 되기 때문입니다. 하지만 " "
하였을 경우는 @
과 *
의미가 틀려집니다. @
은 개개의 원소를 " "
하여 나열하는 것과 같고 *
은 모든 원소를 하나의 " "
안에 넣는 것과 같습니다. 그러므로 array 를 비교할 때는 *
을 사용해야 합니다.
$ AA=(11 22 33)
$ BB=(11 22 33)
$ [ "${AA[*]}" = "${BB[*]}" ]; echo $? # [ "11 22 33" = "11 22 33" ]
0
$ [ "${AA[@]}" = "${BB[@]}" ]; echo $? # [ "11" "22" "33" = "11" "22" "33" ]
bash: [: too many arguments
[[ ]]
이것은 생긴모양에서 알수있듯이 [ ]
의 기능확장 버전입니다.
[ ]
와 가장 큰 차이점은 [ ]
은 명령이고 [[ ]]
은 shell keyword 라는 점입니다.
키워드이기 때문에 일반 명령들과 달리 shell 에서 자체적으로 해석을 해서 실행하기 때문에 [
처럼 명령이라서 생기는 여러가지 제약사항 없이 편리하게 사용할 수 있습니다.
( 좀 더 자세한 내용은 Special Commands 참조 ).
# '<' , '>' 연산자를 escape 하지 않아도 되는 것을 알 수 있습니다.
$ [[ a < b ]]; echo $?
0
$ [[ a > b ]]; echo $?
1
# 사용되는 변수를 quote 하지 않아도 값을 올바르게 인식한다.
$ AA=""
$ [[ -n $AA ]]; echo $?
1
$ [[ -n "$AA" ]]; echo $?
1
Quiz
test 명령에서 값을 비교할 때 8 진수, 16 진수를 사용하려면 어떻게 할까요?
$(( ... ))
산술확장 을 사용하면 됩니다.
$ echo $(( 0xff )) $(( 16#ff )) # 16 진수
255 255
$ echo $(( 0377 )) $(( 8#377 )) # 8 진수
255 255
$ AA=0255; echo $(( 10#$AA )) # 10 진수 ( 0 이 제거된다 )
255
$ test 255 -eq $(( 0xff )) && echo yes
yes
$ test 255 -eq $(( 0377 )) && echo yes
yes
$ test $(( 0377 )) -eq $(( 0xff )) && echo yes
yes
-----------------------------------------------------------
$ readelf -SW /bin/date | grep interp
[ 1] .interp PROGBITS 0000000000000318 000318 00001c 00 A 0 0 1
$ dd skip=$((0x318)) bs=1 count=$((0x1c)) < /bin/date 2> /dev/null
/lib64/ld-linux-x86-64.so.2
-----------------------------------------------------------
$ readelf -SW /bin/date | grep ' .plt '
[13] .plt PROGBITS 0000000000003020 003020 000480 10 AX 0 0 16
$ dd skip=$((0x3020)) bs=1 count=$((0x480)) < /bin/date 2> /dev/null | rasm2 -a x86 -b 64 -S att -BD -f -
0x00000000 6 ff35227d0100 pushq 0x17d22(%rip)
0x00000006 6 ff25247d0100 jmpq *0x17d24(%rip)
0x0000000c 4 0f1f4000 nopl (%rax)
0x00000010 4 f30f1efa endbr64
0x00000014 5 6800000000 pushq $0
0x00000019 5 e9e2ffffff jmp 0
. . .
# rasm2 을 이용하면 x86 이외의 다른 아키텍쳐 binary 도 disassemble 할 수 있습니다.
$ dd skip= ..... | rasm2 -a arm -b 64 -BD -f -