Special Expressions
Shell 은 기본적으로 command arg1 arg2 ...
형식의 명령을 다루지만 그 외에 shell 자체에서 제공하는 표현식이 있습니다. 이것은 shell 에의해서 해석되고 실행되기 때문에 사용방법에 있어서 연산자를 escape 해야 한다든지 변수를 quote 해서 사용해야 하는 제약이 없고 여러가지 편리한 기능들을 제공합니다. $(( ))
, (( ))
, let
은 산술연산에 특화된 기능을, [[ ]]
은 [
명령과 같이 테스트에 특화된 기능을 제공합니다.
sh
에서는 $(( ))
만 사용할 수 있습니다.
$(( ... )) , (( ... ))
먼저 이 표현식은 산술연산을 위한 표현식입니다. 스트링이나 문자는 다루지 않고 오직 숫자만 다룹니다. 그래서 표현식 안에 알파벳으로 된 단어가 오면 그건 변수명이라는 것 외에 다른 의미가 없습니다. 그래서 이 표현식 안에서 변수를 사용할땐 보통 $
문자를 사용하지 않아도 됩니다. $var, var 모두 같은 의미입니다. 그리고 주의할 점은 quotes 을 사용하면 안되고 (숫자만 다루기 때문에 quote 을 할 필요가 없음) 변수값이 숫자가 아니거나 존재하지 않는 변수를 사용할 경우는 0 과 같습니다.
$ A=10
$ (( A = $A + A )) # 변수 A 에 $ 를 붙이지 않아도 된다.
$ echo $A
20
$ A=(5 6 7)
$ (( A[0] = A[1] + ${#A[@]} )) # { } 을 이용한 매개변수 확장은 $ 를 붙여야한다.
$ echo ${A[0]}
9
$(( ))
(( ))
은 생긴건 소괄호로 생겼지만 subshell 이 아닌 현재 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
echo $AA
(( AA-- ))
done
$(( ))
은 괄호 앞에 $
문자가 붙어있어서 $( )
명령치환과 같이 연산 결과를 변수에 대입하거나
명령의 인수 부분에서 사용될 수 있습니다. 하지만 (( ))
과같이 명령행 상에서 단독으로 사용할 수는 없는데요.
이때 :
명령을 활용하면 필요한 연산을 할 수 있습니다.
# 연산 결과값을 변수에 대입시킬 수 있다.
$ AA=$(( 1 + 2 )); echo $AA
3
# 명령의 인수 부분에서 사용할 수 있다.
$ expr 100 + $(( AA + 2 ))
105
# 명령행 상에서 단독으로 사용할 수는 없다.
$ $(( 1 + 2 ))
3: command not found
# 하지만 ':' 명령을 활용하면 명령행 상에서 단독으로 연산을 할 수 있습니다.
$ : $(( AA+=1 ))
$ echo $AA
4
8 진수, 16 진수 연산
숫자가 0
으로 시작하면 8 진수로, 0x
로 시작하면 16 진수로 인식합니다.
$ echo $(( 010 ))
8
$ echo $(( 0x10 ))
16
$ echo $(( 010 + 011 ))
17
$ echo $(( 0x10 + 0xa ))
26
$ echo $(( 010 + 0x10 )) # 8진수 + 16진수
24
$ echo $(( 010 + 0x10 + 10 )) # 8진수 + 16진수 + 10진수
34
Bitwise 연산
- >> (Right Shift) - Shift bits to right and adds '0's on left (Divide by 2 operation)
- << (Left Shift) - Shifts bits to left and add '0' on right (Multiply by 2 operation)
- & (Bitwise AND) - Performs AND operation on every bit and produces result
- | (Bitwise OR) - Performs OR operation on every bit and produces result
- ^ (Bitwise XOR) - Performs XOR operation on every bit and produces result
- ~ (Bitwise NOT) - Inverts all the bits
# Right Shift
echo $(( 15 >> 3 )) # '1111' >> 3 = '0001' ( Ans : 1 )
echo $(( 15 >> 1 )) # '1111' >> 1 = '0111' ( Ans : 7 )
# Left Shift
echo $(( 15 << 3 )) # '1111' << 3 = '1111000' ( Ans : 120 )
echo $(( 15 << 1 )) # '1111' << 1 = '11110' ( Ans : 30 )
# Bitwise AND
echo $(( 15 & 3 )) # '1111' & '0011' = '0011' ( Ans : 3 )
echo $(( 15 & 1 )) # '1111' & '0001' = '0001' ( Ans : 1 )
# Bitwise OR
echo $(( 15 | 3 )) # '1111' | '0011' = '1111' ( Ans : 15 )
echo $(( 15 | 1 )) # '1111' | '0001' = '1111' ( Ans : 15 )
# Bitwise XOR
echo $(( 15 ^ 3 )) # '1111' ^ '0011' = '1100' ( Ans : 12 )
echo $(( 15 ^ 1 )) # '1111' ^ '0001' = '1110' ( Ans : 14 )
# Bitwise NOT
echo $(( ~15 )) # ~ '0000 1111' = '1111 0000' ( Ans : -16 )
echo $(( ~3 )) # ~ '0000 0011' = '1111 1100' ( Ans : -4 )
echo $(( ~1 )) # ~ '0000 0001' = '1111 1110' ( Ans : -2 )
연산자 우선순위는 C 언어와 같습니다.
~
> shift 연산
> <, <=, >, >=
> ==, !=
> &
> ^
> |
> &&
> ||
# shift 연산이 우선순위가 높기 때문에 이것은 2 | ( 4 >> 1 ) 와 같다.
$ echo $(( 2 | 4 >> 1 ))
2
$ echo $(( ( 2 | 4 ) >> 1 ))
3
# &, ^, | 연산은 ==, != 등호 보다도 우선순위가 낮다.
# 따라서 다음 식은 2 | ( 4 == 3 ) 와 같다.
$ echo $(( 2 | 4 == 3 ))
2
$ echo $(( ( 2 | 4 ) == 3 ))
0
for (( i = 0; i < 10; i++ )) 를 할수있다!
(( ))
는 표현식 이라 ;
를 이용해서 여러 명령을 사용할 수는 없는데요. 그래서 (( i = 0; i < 10; i++ )) 만 보자면 오류입니다. 하지만 앞에 for
가 올경우 C 언어 에서처럼 for 문으로 사용할 수 있습니다.
for (( i = 0; i < 10; i++ ))
do
echo $i
done
############### or ################
for (( i = 0; i < 10; i++ )) {
echo $i
}
let
$(( ))
(( ))
표현식은 사용하기에는 편리하지만 비교적 간단한 식을 작성할 때에는 이중괄호 때문에 좀 장황해지는 단점이 있습니다. 그래서 나온 것이 let 명령 입니다.
(( i++ )) res=$(( var + 1 ))
let i++ let res=var+1
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+3
$ let "var++" "res = (var == 5 ? 10 : 20)"; echo $res
10
$ let "var++, res = (var == 5 ? 10 : 20)"; echo $res
20
$ let "2 < 1"; echo $?
1
help let
하면 사용할 수 있는 연산자들을 한눈에 볼 수 있습니다.
[[ ... ]]
이건 생긴 모양에서 알수있듯이 [
명령의 확장 버전입니다. 그래서 [
명령에서 사용할 수 있는 것은 동일하게 사용할 수 있습니다. 그리고 더해서 스트링의 pattern 매칭과 regexp 매칭 기능도 제공합니다.
[
명령에서 스트링 연산자를 이용하여 두값을 비교할 때는 스트링 대 스트링 비교입니다.
하지만 [[ ]]
표현식에서는 스트링 대 스트링
( = ) , 스트링 대 pattern
( = ) , 스트링 대 regexp
( =~ ) 이 가능합니다.
그래서 오른쪽에 pattern 이나 regexp 이 올때는 스트링과 구분하기 위해서 quote 을 해주면 안됩니다.
( 정확히는 glob 문자나 regexp 문자를 quote 에서 제외해야 됩니다. )
왜냐하면 pattern 이나 regexp 전체를 quote 하면 스트링 대 스트링 비교가 되기 때문입니다.
비교되는 스트링은 왼쪽에, pattern 이나 regexp 은 오른쪽에 위치하고 스트링을 변수로 사용할 때는 quote 을 생략할 수 있습니다. 사용되는 regexp 형식은 Extended Regular Expressions 입니다.
sh
에서는 regexp 매칭을 하기 위해서 expr 명령을 사용하면 됩니다.
##################### pattern matching #####################
$ AA='*llo wor*'
$ [[ "hello world" = *llo\ wor* ]]; echo $? # pattern 매칭
0
# glob 문자를 quotes 에서 제외
$ [[ "hello world" = *"llo wor"* ]]; echo $? # pattern 매칭
0
$ [[ "hello world" = $AA ]]; echo $? # pattern 매칭
0
$ [[ "hello world" = "*llo wor*" ]]; echo $? # 스트링 매칭
1
# 패턴에 해당하는 변수를 quote 하였으므로 스트링 매칭
$ [[ "hello world" = "$AA" ]]; echo $? # 스트링 매칭
1
###################### regexp matching ######################
$ [[ "hello world" =~ .*llo\ wor.* ]]; echo $? # regexp 매칭
0
# regexp 문자를 quotes 에서 제외
$ [[ "hello world" =~ .*"llo wor".* ]]; echo $? # regexp 매칭
0
$ [[ "hello world" =~ ".*llo wor.*" ]]; echo $? # 스트링 매칭
1
................................................................
# regexp 에서 공백, <, > 같은 문자는 모두 escape 해줘야 합니다.
$ AA="delete|unset <uuid|vmname>"
$ [[ $AA =~ delete[^\ ]*\ \<uuid\|vmname\> ]]; echo $?
0
# regexp 문자를 quotes 에서 제외
$ [[ $AA =~ delete[^\ ]*" <uuid|vmname>" ]]; echo $?
0
# Extended Regular Expressions 을 사용하므로 ( ) { } 를 escape 하지 않는다.
$ [[ "123123" =~ (123){2} ]]; echo $?
0
...............................................................
# regexp 의 스트링 매칭은 부분만 매칭이 돼도 참이 됩니다.
$ [[ "hello world" =~ "wor" ]]; echo $? # regexp 매칭
0
$ [[ "hello world" = "wor" ]]; echo $? # 스트링 매칭
1
표현식 내에서 자체 &&
, ||
연산자를 제공합니다.
shell 메타문자가 아니므로 우선순위는 프로그래밍 언어와 같이 &&
가 높습니다.
또한 ( )
를 이용해 우선순위 조절을 할 때도 escape 할 필요가 없습니다.
if [[ test1 && test2 ]]; then ...
if [[ test1 || test2 ]]; then ...
if [[ test1 && test2 || test3 ]]; then ...
------------------------------------------
$ [[ 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
=~
연산자를 이용한 regexp 매칭 시에 소괄호 ( )
를 이용해 캡처를 할 수 있습니다.
이때 전체 매칭은 $BASH_REMATCH[0]
에 첫번째 ( )
매칭은 $BASH_REMATCH[1]
... 에 각각 저장됩니다.
#!/bin/bash
string=$1
regexp=$2
if [[ $string =~ $regexp ]]; then
echo "$string matches"
i=0
n=${#BASH_REMATCH[@]}
while [[ $i -lt $n ]]
do
echo " capture[$i]: ${BASH_REMATCH[$i]}"
let i++
done
else
echo "$string does not match"
fi
################## 실행 ###################
$ ./regex1.sh aabbxcc 'a(b{2,3})([xyz])c'
aabbxcc matches
capture[0]: abbxc
capture[1]: bb
capture[2]: x
사용예 )
복합 명령 구성을 할수있는 shell keyword 와 [[ ]]
, IFS 변수, read 명령, BASH_REMATCH 만 있으면 regexp 관련해서는 거의 shell 에서 해결할 수 있습니다.
[[ ! $COLOR =~ BLUE|RED|GREEN ]] && {
echo "Incorrect options provided"
exit 1
}
--------------------------------------------------------------
# 다음은 스트링에서 단어를 분리해 내는 과정입니다.
raw_string='dumpvmcore [--filename=name] info [args...]
injec-tnmi log [[--release] | [--debug]] x [group-settings...]
logdest --release | debug-- [destinations...] log-flags
[[--release] | [--debug]] [flags...] osdetect os osdmesg
[--lines=lines] | pattern'
for word in $( echo -n "$raw_string" | tr -d '|' ); do
[[ $word =~ ^[[:alnum:]-]+{3,}$ ]] &&
[[ ! $word =~ ^-|-$ ]] &&
echo "$word"
done | sort -u
Quiz
다음 테스트문은 어디가 잘못되었을까요?
$ AA=y
$ [ "$AA" = [Yy] ] && yes
$
[
는 특수 표현식이 아니고 명령이므로 [Yy]
는 [
명령의 인수가 되고 globbing 대상입니다.
따라서 현재 디렉토리에 파일 "y" 가 존재한다면 아래의 첫 번째 명령문과 같게되겠지만
그렇지 않을 경우는 두 번째 명령문과 같게 됩니다.
$ [ "y" = "y" ] && yes
yes
$ [ "y" = "[Yy]" ] && 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$ [ "$(echo "$AA" | grep '^[Yy]$')" ] && echo yes
yes