Special Commands
Shell 은 기본적으로 command arg1 arg2 ...
형식의 명령을 다루지만 그 외에 shell 자체에서 제공하는 명령이 있습니다.
이것은 shell 에의해 직접 해석되고 실행되기 때문에 사용방법에 있어서 연산자를 escape 해야 한다든지 변수를 quote 해서 사용해야 하는 제약없이 편리하게 사용할 수 있습니다.
(( ))
, let
는 산술연산에 특화된 기능을, [[ ]]
는 [
명령과 같이 테스트에 특화된 기능을 제공합니다.
special commands 는 bash
에서 제공되는 기능으로 sh
에서는 사용할 수 없습니다.
(( ... ))
먼저 이 명령은 산술연산을 위한 명령으로 스트링은 다루지 않고 숫자만 다룹니다.
따라서 식 안에서 알파벳으로 된 단어가 오면 그것은 변수명이라는 것 외에 다른 의미가 없습니다.
그래서 변수 이름에는 보통 $
문자를 붙이지 않아도 됩니다.
$var
, var
모두 같은 의미입니다.
매개변수 확장과 명령 치환도 사용할 수 있는데 이때는 $
문자를 붙여야 합니다.
test 명령에서 사용되는 -eq
-ne
-le
-ge
-lt
-gt
산술 연산자가 싫을땐
이 명령을 사용하면 됩니다.
변수값이 숫자가 아니거나 존재하지 않는 변수를 사용할 경우는 0 과 같습니다.
$ A=10
$ (( A = $A + A )) # 변수 이름에 '$' 를 붙이지 않아도 된다.
$ echo $A
20
$ A=(5 6 7)
$ (( A[0] = A[1] + ${#A[@]} )) # { } 를 이용하는 매개변수 확장은 '$' 를 붙여야한다.
$ echo ${A[0]}
9
# 명령 치환도 사용할 수 있다
$ B=1000
$ (( B += `date | awk '{print $4}'` ))
$ echo $B
3020
다음과같이 indirection 을 이용한 변수값 대입에 (( ))
를 활용할 수 있습니다.
$ hello=200
$ linux=hello
$ echo ${!linux}
200
$ a=he b=llo
$ (( foo=hello, bar=he$b, zoo=$a$b, baz=$linux ))
$ echo $foo $bar $zoo $baz
200 200 200 200
(( ))
는 생긴건 소괄호로 생겼지만 subshell 이 아닌 현재 shell 에서 실행됩니다.
그래서 현재 shell 의 변수값을 직접 변경할 수 있습니다.
식을 작성할 때 사용할수 있는 연산자들은 C 언어와 같습니다.
var++
, --var
, a ? b : c
, ( )
을 이용한 연산자 우선순위 조절, ,
연산자 등 모두 사용할 수 있습니다.
( 거듭제곱은 **
)
정리하면 (( ))
내에서는 그냥 프로그래밍 언어를 한다고 생각하면 됩니다.
참, 거짓 판단도 동일하게 0
이 거짓이고 그외 숫자가 참입니다. ( 산술연산 이기 때문에 )
$ (( 1 < 2 )); echo $? # (( )) 안에서 참으로 종료됐으므로 종료상태 값은 0
0
$ (( 1 > 2 )); echo $? # (( )) 안에서 거짓으로 종료됐으므로 1
1
$ (( 0 )); echo $? # 산술연산에서 0 은 거짓 이므로 종료상태 값은 1
1
$ (( 1 )); echo $? # 산술연산에서 0 이외의 값은 참 이므로 종료상태 값은 0
0
$ (( var = 0, res = (var ? 3 : 4) ))
$ echo $res
4
$ (( var = 1, res = (var ? 3 : 4) ))
$ echo $res
3
$ AA=10
$ while (( AA > 5 )); do $ for (( i = 0; i < 10; i++ )); do
echo $AA echo $i
(( AA-- )) done
done
예를들어 opt1, opt2, opt3 세 개의 변수의 합이 5 보다 클 경우를
조건문에서 사용할 경우 기존의 test 명령을 사용하는 것보다 (( ))
를 사용하면
식이 더 간단해집니다.
let "res = opt1 + opt2 + opt3" if (( opt1 + opt2 + opt3 > 5 )); then
if [ "$res" -gt 5 ]; then . . .
. . . fi
fi
let
Shell 에서는 기본적으로 모든 값이 스트링으로 처리되므로 숫자로 다루어야 할 경우 문제가 됩니다.
이때 let 을 이용하면 산술연산을 할 수 있습니다.
$ res=100
$ res+=20
$ echo $res # 숫자가 스트링으로 처리되어 concatenation 이 된다.
10020
$ res=100
$ let res+=20 # let 을 이용하면 산술연산을 할 수 있습니다.
$ echo $res
120
let 은 한줄에 여러 개의 산술식을 사용할 수 있습니다. 이때 하나의 식은 하나의 인수와 매칭이 되어야 하므로 식을 쓸때는 공백 없이 붙여 써야 합니다. 하지만 quotes 을 이용하면 공백을 사용할 수 있습니다.
$ let var = 1 + 2
bash: let: =: syntax error: operand expected (error token is "=")
$ let var += 1
bash: let: +=: syntax error: operand expected (error token is "+=")
$ let var=1+2 # 식을 쓸땐 공백없이 붙여서 하나의 인수가 되어야한다.
OK
$ i=2 val=200
$ let aa1=100 aa$i=val res=aa1+aa$i # 한줄에 여러개의 식을 사용할 수 있다.
$ echo $res
300
$ let var=1+3 "res = (var == 5 ? 10 : 20)" # quote 을하면 공백을 사용할 수 있다.
$ echo $res
20
$ let "var++, res = (var == 5 ? 10 : 20)"
$ echo $res
10
$ let "2 < 1"; echo $?
1
help let
하면 사용할 수 있는 연산자들을 한눈에 볼 수 있습니다.
[[ ... ]]
이건 생긴 모양에서 알수있듯이 [
명령의 확장 버전입니다. 그래서 [
명령에서 사용할 수 있는 것은 동일하게 사용할 수 있습니다. 그리고 더해서 스트링의 pattern 매칭과 regex 매칭 기능도 제공합니다.
[
명령에서 스트링 연산자를 이용하여 두 값을 비교할 때는 스트링 대 스트링 비교입니다.
하지만 [[ ]]
명령에서는 스트링 대 스트링
( == ) , 스트링 대 pattern
( == ) , 스트링 대 regex
( =~ ) 이 가능합니다.
그래서 오른쪽에 pattern 이나 regex 이 올때는 스트링과 구분하기 위해서 quote 을 해주면 안됩니다
( 정확히는 glob 문자나 regex 문자를 quote 에서 제외해야 됩니다 ).
왜냐하면 pattern 이나 regex 를 quote 하면 스트링 대 스트링 비교가 되기 때문입니다.
연산자를 중심으로 비교되는 스트링은 왼쪽에, pattern 이나 regex 은 오른쪽에 위치합니다. 비교되는 스트링을 변수로 사용할 때는 quote 을 생략할 수 있습니다. 사용되는 regex 형식은 Extended Regular Expressions 입니다.
sh
에서는 regex 매칭을 하기 위해서 expr 명령을 사용하면 됩니다.
##################### pattern matching #####################
$ pat='*llo wor*'
$ [[ "hello world" == *llo\ wor* ]]; echo $? # pattern 매칭
0
# glob 문자만 quote 에서 제외해도 된다.
$ [[ "hello world" == *"llo wor"* ]]; echo $? # pattern 매칭
0
$ [[ "hello world" == $pat ]]; echo $? # pattern 매칭
0
$ [[ "hello world" == "*llo wor*" ]]; echo $? # 스트링 매칭
1
# 패턴에 해당하는 변수를 quote 하였으므로 스트링 매칭
$ [[ "hello world" == "$pat" ]]; echo $? # 스트링 매칭
1
###################### regex matching #######################
$ regex='.*llo wor.*'
$ [[ "hello world" =~ .*llo\ wor.* ]]; echo $? # regex 매칭
0
# regex 문자만 quote 에서 제외해도 된다.
$ [[ "hello world" =~ .*"llo wor".* ]]; echo $? # regex 매칭
0
$ [[ "hello world" =~ $regex ]]; echo $? # regex 매칭
0
$ [[ "hello world" =~ ".*llo wor.*" ]]; echo $? # 스트링 매칭
1
# regex 에 해당하는 변수를 quote 하였으므로 스트링 매칭
$ [[ "hello world" =~ "$regex" ]]; echo $? # 스트링 매칭
1
................................................................
# 공백, <, >, | 같은 문자는 모두 escape 해줘야 한다.
$ str="delete|unset <uuid|vmname>"
$ [[ $str =~ delete[^\ ]*\ \<uuid\|vmname\> ]]; echo $?
0
# regex 만 남기고 나머지는 quote 을하면 간단해진다.
$ [[ $str =~ delete[^\ ]*" <uuid|vmname>" ]]; echo $?
0
# regex 을 변수에 대입해서 사용하면 더 간단해진다.
$ regex='delete[^ ]* <uuid\|vmname>'
$ [[ $str =~ $regex ]]; echo $?
0
# 또는 다음과 같이 ( ) 괄호를 사용할 수도 있는데 이경우도 >(...) or <(...) 와같이
# 프로세스 치환 형태가 될경우는 >, < 를 escape 해줘야 됩니다. 예) ((.*)<[^>]+\>(.*))
[[ $str =~ (delete[^ ]* <uuid\|vmname>) ]]; echo $?
0
# escape 문자를 사용하려면 다음과 같이 $' ' 를 사용하면 됩니다.
[[ $str == *foo$'\n'bar* ]]
[[ $str =~ foo[$' \t\n']+bar ]] # 또는 foo[$IFS]+bar
...............................................................
# regex 의 스트링 매칭은 부분만 매칭이 돼도 참이 됩니다.
$ [[ "hello world" =~ "wor" ]]; echo $? # regex 매칭
0
$ [[ "hello world" == "wor" ]]; echo $? # 스트링 매칭
1
regex 매칭시 [[ ... ]]
안에서는 \s
, \w
, \b
같은
확장 기능 은 사용할 수 없습니다.
str='foo bar zoo'
$ [[ $str =~ ^\w+\s+ ]] && echo yes # 사용할 수 없다.
$
$ [[ $str =~ ^[[:alnum:]_]+[[:space:]]+ ]] && echo yes
yes
$ regex='^\w+\s+' # 변수를 사용할 경우는 가능
$ [[ $str =~ $regex ]] && echo yes
yes
$ [[ $str =~ $(echo '^\w+\s+') ]] && echo yes
yes
비교되는 값이 array 변수일 경우는 ( $@
, $*
포함 ) 전체 원소가 하나의 값으로 사용됩니다.
$ arr=( foo bar zoo )
$ [[ ${arr[@]} =~ ^bar$ ]] && echo yes # 원소 단위 매칭이 안됨
$
$ [[ ${arr[@]} =~ ^foo\ bar ]] && echo yes
yes
$ [[ ${arr[@]} == foo\ bar* ]] && echo yes
yes
$ [[ ${arr[@]} =~ bar ]] && echo yes
yes
$ [[ ${arr[@]} == *bar* ]] && echo yes
yes
다음은 [[ ]]
명령을 사용할때 주의할 점입니다.
# 스트링 비교시 값에 escape 할때 사용하는 `\` 문자가 포함될경우 quote 을 해줘야한다
$ val='\"aaa bbb\"'
$ [[ $val == $val ]] && echo yes
$
$ [[ $val == "$val" ]] && echo yes # quote 을 해줘야한다.
yes
$ [[ $val =~ $val ]] && echo yes
$
$ [[ $val =~ "$val" ]] && echo yes # quote 을 해줘야한다.
yes
---------------------------------------------------------------
# test 명령에서 사용되는 옵션이 스트링 비교에 사용될경우 quote 을 해줘야 한다.
$ val1="-e|-f|-g|-h"
$ [[ -f == @($val1) ]] && echo yes # ERROR
$ [[ "-f" == @($val1) ]] && echo yes # OK
yes
$ val2="-f" # 변수를 사용할 경우는 괜찮다.
$ [[ $val2 == @($val1) ]] && echo yes # OK
yes
$ [[ $val2 ]] && echo yes # OK
yes
AND, OR
[[ ]]
에서는 자체 &&
, ||
연산자를 제공합니다.
따라서 [ ]
명령에서 사용되는 -a
(and), -o
(or) 은 사용할 수 없습니다.
shell 메타문자가 아니므로 우선순위는 프로그래밍 언어와 같이 &&
가 높습니다.
또한 ( )
를 이용해 우선순위 조절을 할 때도 escape 할 필요가 없습니다.
$ [[ A == A -a B == B ]] && echo YES # -a, -o 연산자는 사용할 수 없다.
bash: syntax error in conditional expression
bash: syntax error near '-a'
if [[ A == A && B == B ]]; then ... # OK
if [[ A == A || B == B ]]; then ... # OK
if [[ A == A && B == B || C == C ]]; then ... # OK
------------------------------------------
$ [[ 1 -eq 1 || 1 -eq 2 && 1 -eq 2 ]]; echo $? # && 연산자가 우선순위가 높다
0
# '( )' 를 이용해 우선순위 조절을 할때는 '[' 명령과 달리 escape 할 필요가 없습니다.
$ [[ ( 1 -eq 1 || 1 -eq 2 ) && 1 -eq 2 ]]; echo $?
1
$ [[ 1 -eq 1 ]] || [[ 1 -eq 2 ]] && [[ 1 -eq 2 ]]; echo $?
1
BASH_REMATCH
=~
연산자를 이용한 regex 매칭시에 소괄호 ( )
를 이용해 캡처를 할 수 있습니다.
이때 전체매칭은 $BASH_REMATCH[0]
에 첫번째 ( )
매칭은 $BASH_REMATCH[1]
... 에 각각 저장됩니다.
#!/bin/bash
string=$1
regex=$2
if [[ $string =~ $regex ]]; then
echo "$string matches"
for (( i = 0; i < ${#BASH_REMATCH[@]}; i++ )); do
echo " capture[$i]: ${BASH_REMATCH[i]}"
done
else
echo "$string does not match"
fi
############ 실행 결과 #############
$ ./test.sh aabbxcc 'a(b{2,3})([xyz])c'
aabbxcc matches
capture[0]: abbxc
capture[1]: bb
capture[2]: x
regex 에 .*
를 사용하지 않을 경우 앞에서부터 매칭이 됩니다.
$ foo='aaa 123= bbb 456= ccc 789= ddd'
$ [[ $foo =~ ([0-9]+=) ]]
$ echo ${BASH_REMATCH[1]} # 첫번째 매칭
123=
$ [[ $foo =~ .*" "([0-9]+=) ]] # ".*" 를 사용
$ echo ${BASH_REMATCH[1]} # 마지막 매칭
789=
사용예 )
#!/bin/bash
while read -r line; do
if [[ $line =~ ^([0-9]+)\ 번서버$ ]]; then # ([0-9]+) 를 이용해 숫자를 캡춰해서
ofile=${BASH_REMATCH[1]}_server.txt # ofile 파일명을 설정하는데 사용.
echo "set output file as $ofile"
elif [[ -n $line ]]; then
echo "$line" >> "$ofile"
fi
done
입력값에서 <...>
와 [...]
형태의 스트링을 모두 제거합니다.
string='VBoxManage cloudprofile <--provider=name> <--profile=name>
update [--clouduser=unique id] [--fingerprint=MD5 string] [--keyfile=path]'
while [[ $string =~ (.*)\<[^\>]+\>(.*) ]]; do
string=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
while [[ $string =~ (.*)\[[^]]+](.*) ]]; do
string=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
echo $string
######### 실행 결과 #########
VBoxManage cloudprofile update
--[no-]foo
형태를 --foo
와 --no-foo
로 확장합니다.
string='--[no-]foo --[dont-]bar --zoo'
options=( $string )
for ((i = 0; i < ${#options[*]}; i++)); do
option=${options[i]}
if [[ $option =~ (\[(no-|dont-)]) ]]; then
option1=${option/${BASH_REMATCH[1]/[/\\[}/} # --foo
option2=${option//[][]/} # --no-foo
echo "$option1 $option2"
else
echo "$option"
fi
done
######## 실행 결과 ########
--foo --no-foo
--bar --dont-bar
--zoo
확장 패턴
[ ]
일반 명령과 달리 [[ ]]
에서는 확장패턴 을 사용할 수 있습니다.
$ COLOR="RED"
$ [[ $COLOR == @(RED|GREEN|BLUE) ]] && echo yes # @( )
yes
$ [[ $COLOR =~ RED|GREEN|BLUE ]] && echo yes # =~
yes
--------------------------------------------
$ COLOR="RED color"
$ [[ $COLOR == @(RED|GREEN|BLUE) ]] && echo yes
$
$ [[ $COLOR =~ RED|GREEN|BLUE ]] && echo yes
yes
Quiz
특정 변수에 값이 존재하는지 체크할 때 다음과 같이 [[ ]]
를 활용할 수 있습니다.
$ SOME_OPTIONS="-a -b"
# 변수가 존재하지 않거나 값이 empty 일 경우
$ [[ $SOME_OPTIONS ]] && echo yes $ [[ ! $NOT_EXIST ]] && echo yes
yes yes
# 위 문장은 다음과 같은것 입니다. # 위 문장은 다음과 같은것 입니다.
$ test -n "$SOME_OPTIONS" && echo yes $ test -z "$NOT_EXIST" && echo yes
yes yes
2 .
다음 테스트문은 어디가 잘못되었을까요?
$ AA=y
$ [ "$AA" = [Yy] ] && echo yes
$
[
는 일반 명령이므로 [Yy]
는 [
명령의 인수가 되고 globbing 대상입니다.
따라서 현재 디렉토리에 파일 y
가 존재한다면 아래의 첫 번째 명령문과 같게되겠지만
그렇지 않을 경우는 두 번째 명령문과 같게 됩니다.
$ [ "y" = "y" ] && echo yes
yes
$ [ "y" = "[Yy]" ] && echo yes
$
sh
에서는 [[ ]]
명령을 사용할 수 없으므로 다음과 같은 방법을 사용하면 됩니다.
sh$ AA=y
sh$ case $AA in ([Yy]) echo yes; esac # case 문에서는 pattern 사용이 가능하므로
yes
sh$ expr "x${AA}" : "x[Yy]$" > /dev/null && echo yes
yes
sh$ [ -n "$(echo "$AA" | grep '^[Yy]$')" ] && echo yes
yes