Test Operators

이 연산자들은 test, [ 명령에서 제공하는 것으로 [[ ]] 에서도 동일하게 사용할 수 있습니다.
( 단 -a, -o 연산자는 제외. [[ ]] 에서는 자체 && , || 연산자를 제공합니다.)

File Tests

파일이 실행파일인지 테스트할 때 사용하는 -x-r, -w 연산자는 파일의 drwxrwxr-x 모드 값을 가지고 판단합니다. 그러므로 디렉토리도 테스트에 성공할 수 있습니다. 디렉토리를 제외하고 실행 파일인지 테스트하려면 다음과 같이 합니다.

$ if [ -f "$file" -a -x "$file" ]; then ... fi

$ if [[ -f "$file" && -x "$file" ]]; then ... fi

연산자는 test -fx "$file" 와 같이 두개를 붙여 쓰기 할 수 없습니다.

test 되는 파일이 symbolic link 일경우 -L, -h 연산자는 링크 자체를 테스트하고 나머지는 링크에 연결된 대상 파일을 테스트합니다.

연산자 설명
-a <FILE> 파일이 존재하면 true 입니다.
( Logical AND 연산자와 구분이 어려우므로 사용하지 않는 것을 권장. )
-e <FILE> 파일이 존재하면 true 입니다.
-f <FILE> 파일이 존재하고 regular 파일이면 true 입니다.
( named pipe 나 <( ... ) 는 false 가 됩니다. )
-d <FILE> 파일이 존재하고 directory 이면 true 입니다.
-c <FILE> 파일이 존재하고 character special 파일이면 true 입니다.
( 터미널도 character special 에 해당합니다. )
-b <FILE> 파일이 존재하고 block special 파일이면 true 입니다.
-p <FILE> 파일이 존재하고 (named, unnamed) pipe 이거나 <( ... ) 이면 true 입니다.
-S <FILE> 파일이 존재하고 socket 이면 true 입니다.
-L <FILE> 파일이 존재하고 symbolic link 이면 true 입니다.
-h <FILE> 파일이 존재하고 symbolic link 이면 true 입니다.
-g <FILE> 파일이 존재하고 sgid bit 이 설정돼 있으면 true 입니다.
-u <FILE> 파일이 존재하고 suid bit 이 설정돼 있으면 true 입니다.
-k <FILE> 파일이 존재하고 sticky bit 이 설정돼 있으면 true 입니다.
-r <FILE> 파일이 존재하고 readable 이면 true 입니다.
-w <FILE> 파일이 존재하고 writable 이면 true 입니다.
-x <FILE> 파일이 존재하고 executable 이면 true 입니다.
-O <FILE> 파일이 존재하고 uid (user id) 가 같으면 true 입니다.
-G <FILE> 파일이 존재하고 gid (group id) 가 같으면 true 입니다.
-N <FILE> 파일이 존재하고 마지막에 read 한뒤로 modify 되었으면 true 입니다.
-s <FILE> 파일이 존재하고 사이즈가 0 보다 크면 (not empty) true 입니다.
-t <fd> FD 가 존재하고 현재 터미널에 연결돼 있으면 true 입니다.
<FILE1> -nt <FILE2> FILE1 이 FILE2 보다 수정시간이 newer 면 true 입니다.
<FILE1> -ot <FILE2> FILE1 이 FILE2 보다 수정시간이 older 면 true 입니다.
<FILE1> -ef <FILE2> FILE1 과 FILE2 가 서로 하드링크 되어 있으면 true 입니다.

파일이나 디렉토리가 존재하는지 체크할때 readlink 명령을 이용할 수도 있습니다. readlink 명령은 경로에 symbolic link 가 포함될 경우 모두 추적하여 physical 경로를 출력해 줍니다.

$ readlink -e /usr/bin/google-chrome
/opt/google/chrome/google-chrome

$ readlink -e /lib64/ld-linux-x86-64.so.2 
/lib/x86_64-linux-gnu/ld-2.28.so

예제 )

다음은 두개의 파일을 인수로 받는 스크립트에서 test 명령을 이용해 인수를 체크하는 예입니다.
파일은 <( ... ) 나 named pipe 가 올수 있으므로 -f 로 체크하지 않고 디렉토리 여부만 체크하였습니다.

#!/bin/sh

test $# != 2 && { echo two arguments required; exit 1 ;}
for file
do
    test "$file" = - && continue   # 인수로 stdin 을 나타내는 '-' 가 사용될 경우
    test -e "$file" || { echo "$file" does not exist; exit 1 ;}
    test -d "$file" && { echo "$file" is a directory; exit 1 ;}
    test -r "$file" || { echo "$file" is not readable; exit 1 ;}
done
. . .

String Tests

보통 프로그래밍 언어에서 < , > 는 숫자를 비교할 때 사용되는데 shell 에서는 특이하게 스트링을 비교하는데 사용됩니다. 생각 같아서는 스트링 연산자와 산술 연산자가 서로 바뀌었으면 헷갈리지 않고 좋을것 같은데 사실 이것도 알고보면 shell script 가 갖는 근본적인 한계입니다. 왜냐하면 shell 에서는 <= , >= 와 같은 연산자를 사용하기가 어렵습니다. 이렇게 하게되면 = 가 파일명이 되고 < , > 가 redirection 기호가 되기 때문입니다.

예를 들어 expr 명령은 산술연산을 하는 명령인데 연산자를 사용할때는 모두 \< \<= \> =\> \& \| \* 와 같이 escape 해야 합니다.

< , > 연산자는 사전적으로 (lexicographically) 비교하기 때문에 숫자를 비교하는데 사용하면 안됩니다 (100 보다 2 가 크다고 나옵니다). 또한 shell redirection 메타문자와 충돌하므로 사용할땐 escape 해야 합니다.

연산자 설명
-z <STRING> 스트링이 empty 면 true 입니다.
( 변수가 존재하지 않는 경우에도 해당됩니다. )
-n <STRING> 스트링이 empty 가 아니면 true 입니다.
<STRING1> = <STRING2> 두 스트링 값이 같으면 true 입니다.
<STRING1> != <STRING2> 두 스트링 값이 다르면 true 입니다.
<STRING1> < <STRING2> 사전적으로 비교했을 때 스트링1 이 작으면 true 입니다.
( 사용할땐 escape 해야됨. )
<STRING1> > <STRING2> 사전적으로 비교했을 때 스트링1 이 크면 true 입니다.
( 사용할땐 escape 해야됨. )

Arithmetic Tests

test 명령의 산술 연산자는 기본적으로 정수값만 다룹니다. 숫자 이외의 문자가 포함되거나 소수점이 사용될 경우는 오류가 됩니다. ( 알파벳 문자를 연산자로 사용하는 것은 fortran 과 비슷하죠 )

연산자 설명
<INTEGER1> -eq <INTEGER2> 두 수가 같으면 true 입니다.
<INTEGER1> -ne <INTEGER2> 두 수가 같지 않으면 true 입니다.
<INTEGER1> -le <INTEGER2> INT1 이 INT2 보다 작거나, 두 수가 같으면 true 입니다.
<INTEGER1> -ge <INTEGER2> INT1 이 INT2 보다 크거나, 두 수가 같으면 true 입니다.
<INTEGER1> -lt <INTEGER2> INT1 이 INT2 보다 작으면 true 입니다.
<INTEGER1> -gt <INTEGER2> INT1 이 INT2 보다 크면 true 입니다.

보통 equal 연산에는 숫자를 스트링으로 취급해서 = 연산자를 사용할 수도 있고 -eq 연산자를 사용할 수도 있는데 다음과 같은 차이점이 있습니다.

$ [ 123 = 123 ]; echo $?      # 숫자를 스트링으로 취급해도 된다.
0

$ [ 123 -eq 123 ]; echo $?
0

$ [ 001 = 1 ]; echo $?        # 이경우 '=' 연산자는 거짓이 되고
1

$ [ 001 -eq 1 ]; echo $?      # '-eq' 연산자는 참이된다.
0

비교가 참일 경우 종료 상태 값으로 0 을, 거짓이면 1 을 반환하고 그 외 비교 값이 정수가 아니거나 오류가 발생하면 2 를 반환합니다. 따라서 다음과 같이하면 입력값이 정수인지 판단할 수 있습니다.

$ alias bashx='bash <<\@ -s'
--------------------------------------------------------------------------

$ bashx                                       $ bashx
num=-123                                      num=1.23

test "$num" -eq 0 2> /dev/null;               test "$num" -eq 0 2> /dev/null;

if [ $? -lt 2 ]; then                         if [ $? -lt 2 ]; then
    echo "integer"                                echo "integer"
else                                          else
    echo "not an integer"                         echo "not an integer"
fi                                            fi
@                                             @

integer                                       not an integer

Misc Syntax

and, or 연산자로 &&, || 를 사용하면 shell 메타문자가 돼버리므로 대신에 -a, -o 를 사용합니다.

연산자 설명
<TEST1> -a <TEST2> 두 테스트 간에 AND 연산을 할 때 사용합니다.
-a 연산자는 파일 테스트에서도 사용되므로 주의해야 합니다.
<TEST1> -o <TEST2> 두 테스트 간에 OR 연산을 할 때 사용합니다.
! <TEST> Logical NOT 연산을 합니다.
( <TEST> ) 우선순위 조절을 위해 사용할 수 있습니다 ( 사용할땐 escape 합니다. )
-o <OPTION_NAME> set builtin 명령으로 설정하는 옵션의 현재 값을 테스트할 때 사용합니다.
현재 -o 상태면 true, +o 상태면 false 입니다.
-v <VARIABLENAME> 변수가 존재하는지 테스트할 때 사용합니다.
존재하지 않는 상태 (unset 상태) 면 1 을, 그 외에는 0 을 리턴합니다.
-R <VARIABLENAME> 변수가 named reference 이면 true 입니다. (bash version 4.3+).

우선순위 조절 예제

#!/bin/bash

echo -n "Enter a character: "
char=$(head -c1)

# 입력값이 0 ~ 9 문자이면 참
if test \( "$char" \> 0 -o "$char" = 0 \) -a \( "$char" \< 9 -o "$char" = 9 \)
then
    echo "It's a number"
else
    echo "It's not a number"
fi
-----------------------------

# -ge 산술 연산자의 경우 숫자 이외의 문자를 입력하면 오류가 되므로 다음과 같이 작성해도 됩니다.
if test "$char" -ge 0 2> /dev/null
then
    echo "It's a number"
else
    echo "It's not a number"
fi

변수값이 연산자와 같은 문자일 경우

변수값이 테스트 명령에서 사용되는 연산자와 같은 스트링일 경우 옛날 버전의 test 명령에서는 오류가 발생한다고 합니다. 따라서 문제를 해결하기 위해 변수앞에 특정 문자를 붙여서 작성하는 경우가 있습니다.

# $var 값이 -a 이라면 [ -a = "" ] 가 되어 옛날 버전의 test 명령에서 오류 발생
$ if [ "$var" = "" ]; then ...

# 다음과 같이 작성하면 [ x-a = x ] 가 되므로 문제가 생기지 않는다.
$ if [ x"$var" = x ]; then ...

test 명령에서 사용되는 equal 연산자

현재는 값이 같은지 비교할 때는 == 연산자를 사용하고 대입에는 = 연산자를 사용하지만 과거에는 비교에도 = 연산자를 사용했습니다. 이것이 아직 sh 에 남아 있어서 sh 에서는 test, [ ] 명령에서 값을 비교할때 = 연산자를 사용해야 오류가 발생하지 않습니다. bash 의 경우는 과거 호환을 위해 test, [ ], [[ ]] 명령에서 =, == 두 가지 모두 사용이 가능하지만 되도록이면 == 연산자를 사용하는 것이 좋습니다.

# '=' 연산자는 test, '[ ]' 명령에 한해서 사용하는 것이 좋습니다.
$ foo=123

$ if [[ $foo = 455 ]]; then echo yes; else echo no; fi
no
$ if (( foo = 456 )); then echo yes; else echo no; fi     # (( )) 명령 에서는
yes                                                       # 대입 연산이 된다.
$ echo $foo
456

Quiz

newline 이나 tab 문자는 어떻게 비교할까요?

# bash 의 경우

if [ "$char" = $'\n' ]; then ...

if [ "$char" = $'\t' ]; then ...

-----------------------------------

# sh 의 경우

nl='                    # 명령치환은 newline 저장이 안되므로
'                       # nl=$(echo "\n") 는 사용할 수 없다.
tab=$(echo "\t")

if [ "$char" = "$nl" ]; then ...      # 변수는 quote 해야 합니다. 

if [ "$char" = "$tab" ]; then ...

2 .

파일이 바이너리 파일인지 아닌지는 어떻게 구분할까요?

if [ -f "$filename" -a "$(file -b --mime-encoding "$filename")" = binary ]
then
    echo It\'s a binary file
fi

test, expr 명령은 기본적으로 정수값만 다룰 수 있는데요. float 값을 비교하려면 어떻게 할까요?

$ val1=12.1 val2=5.28

# bc 명령은 표현식이 참일때 1 을 출력합니다.
$ if [ 1 = $( echo "$val1 > $val2" | bc ) ]; then echo yes; fi
yes

$ if [ 1 = $( echo "$val1 < $val2" | bc ) ]; then echo yes; fi
$

$ res=$( echo "$val1 + $val2" | bc ); echo $res
17.38

# 초월수중에 하나인 Champernowne constant
$ bc <<< 'scale=1000; 60499999499 / 490050000000'
.1234567891011121314151617181920212223242526272829303132333435363738\
39404142434445464748495051525354555657585960616263646566676869707172\
73747576777879808182838485868788899091929394959697  990001020304050607\
. . .
$ bc <<< 'scale=1000; 1 / 998001' 
.0000010020030040050060070080090100110120130140150160170180190200210\
22023024025026027028029030031032033034035036037038039040041042043044\
04504604704804905005105205305405505605705805906006106206306406506606\
. . .

Gregory–Leibniz series 를 이용해 pi 값 구해보기

$ seq -f ' 4/%g ' 1 2 20 | paste -s -d '-+'
 4/1 - 4/3 + 4/5 - 4/7 + 4/9 - 4/11 + 4/13 - 4/15 + 4/17 - 4/19

# 소수점 넷째 자리부터는 정확하지 않다.
$ seq -f '4/%g' 1 2 9999 | paste -s -d '-+' |  bc -l
3.14139265359179323814

# a() 는 arctangent function
$ bc -l <<< "scale=1000; 4 * a(1)"
3.141592653589793238462643383279502884197169399375105820974944592307\
81640628620899862803482534211706798214808651328230664709384460955058\
22317253594081284811174502841027019385211055596446229489549303819644\
. . .

테일러 시리즈를 이용해 자연상수 e 구해보기

$ awk 'BEGIN {
    for (i = 1; i <= 10; i++) {
        str = "(1"
        for (j = 1; j < i; j++) str = str "*" j
        print "1/" str ")"
    }
}'
1/(1)
1/(1*1)
1/(1*1*2)
1/(1*1*2*3)
1/(1*1*2*3*4)
1/(1*1*2*3*4*5)
1/(1*1*2*3*4*5*6)
. . .
$ awk 'BEGIN {
    for (i = 1; i <= 10; i++) {
        str = "(1"
        for (j = 1; j < i; j++) str = str "*" j
        print "1/" str ")"
}}' | paste -s -d '+' | bc -l
2.71828152557319223982

$ bc -l <<< 'e(1)'
2.71828182845904523536

bc 명령은 arbitrary-precision arithmetic 연산기로 내부적으로 모든 연산을 10 진수로 처리하기 때문에 2 진수로 처리되는 float, double 연산과 달리 정확한? 값을 출력합니다. -l ( --mathlib ) 옵션을 이용하면 다음과 같은 수학함수도 사용할 수 있습니다. ( 이때 scale 은 20 으로 설정됩니다.)

s(x)     The sine of x, x is in radians.  
c(x)     The cosine of x, x is in radians.  
a(x)     The arctangent of x, arctangent returns radians.  
l(x)     The natural logarithm of x.  
e(x)     The exponential function of raising e to the value x.  
j(n,x)   The Bessel function of integer order n of x.

16 진수를 사용할땐 대문자를 사용해야 합니다. ( 소문자는 변수 이름으로 사용됨 )

신세대 계산기로 qalculate 도 있습니다. bc 명령 처럼 10 진수로 연산을 하고 속도도 빠릅니다. 다음과 같이 명령 라인에서도 사용할 수 있습니다.

$ sudo apt install qalculate-gtk

$ qalc 'factor(15x^3 + 92x^2 + 89x + 20)'
(15 × (x^3)) + (92 × (x^2)) + (89 × x) + 20 = (3x + 1)(5x + 4)(x + 5)

$ qalc 'solve((3x + 1)(5x + 4)(x + 5) = 0)'
solve((((3 × x) + 1) × ((5 × x) + 4) × (x + 5)) = 0) = [−54/51/3] ≈ [−50.80.3333333333]

$ qalc 'multisolve([ x + y = 10, x - y = 2 ], [x, y])'    # x, y 대신에 \a, \b 형태도 가능
multisolve([((x + y) = 10)  ((x − y) = 2)], [x  y]) = [6  4]

$ qalc '(\a + \b)^4'
('a' + 'b')^4 = 'a'^4 + 4'a'^3 × 'b' + 6'a'^2 × 'b'^2 + 4'a''b'^3 + 'b'^4

$ qalc 'diff(x * sin(x))'
diff(x × sin(x radians)) = cos(x) × x + sin(x)

$ qalc 'integrate(cos(x) × x + sin(x))'
integrate((cos(x radians) × x) + sin(x radians)) = sin(x) × x + C

$ qalc 'plot(x * sin(x), -500, 500)'            # gnuplot 패키지 설치 필요
plot(x × sin(x radians), −500, 500) = 0

$ qalc 'arcsin(1/2) to deg'                     # 30 deg to rad ...
arcsin(1 / 2) = 30°

$ qalc 'sin(30 deg)'
sin(30 × degree) = 1/2 = 0.5

$ qalc '[1 2 3; 4 5 6] * [7 8; 9 10; 11 12]'    # matrix multiplication
[1  2  3; 4  5  6] * [7  8; 9  10; 11  12] = [58  64; 139  154]

$ qalc '[1 2 3] . [4 5 6]'     # dot product
dot([1  2  3], [4  5  6]) = 32

$ qalc 'e^(pi * i)'
e^(pi × i) = −1

$ echo 'e^(pi * i / 2)' | qalc -t -c0 | sed -E '/^>|^$/d'
  i

$ echo '60499999499 / 490050000000' | qalc -t -c0 -s "precision 200" | sed -E '/^>|^$/d'
  0.1234567891011121314151617181920212223242526272829303132333435363738394041424
  344454647484950515253545556575859606162636465666768697071727374757677787980818
  2838485868788899091929394959697990001020304051

과학, 공학 분야에서의 전문적인 계산에는 julia 언어를 사용할 수 있습니다.

3 .

Shell 에서는 입, 출력이 terminal 이나 pipe, regular file 로 연결될 수 있습니다.
현재 stdin 이 어디에 연결되어 있는지 어떻게 알 수 있을까요?

$ test -t 0; echo $?                         # stdin 이 terminal 에 연결되어 있는지 테스트
0

$ test -f /dev/stdin < /etc/hosts; echo $?   # stdin 이 file 에 연결되어 있는지 테스트
0

$ echo 111 | test -p /dev/stdin; echo $?     # stdin 이 pipe 에 연결되어 있는지 테스트
0

-------------------------------------------

$ check() {
    local res=`stat -L -c "%F" /dev/stdin`
    case $res in
        "character special file") 
            test -t 0 && echo terminal || echo "$res" ;;
        "fifo")          echo pipe ;;
        "regular file")  echo file ;;
    esac
}

$ check
terminal

$ echo 111 | check 
pipe

$ check < /etc/hosts
file

$ check < /dev/null
character special file