본문 바로가기

IT

[Ubuntu/Linux] 쉘스크립트 expr의 모든 것

제어문과 변수까지 살펴보니 expr가 남았네요.

expr까지 간단하게 살피고 나서는,

쉘스크립트 자체 문법보다는 쉘스크립트와 함께 쓰이는 유용한 명령어들을 살펴보겠습니다.


쉘스크립트가 명령어를 좀 더 지능적으로 사용하려는 의도에서 만들어졌기 때문에,

이 다음부터가 본편이라 볼 수 있겠네요.



expr는 명령어로서 존재합니다.

$ ls /usr/bin/expr
/usr/bin/expr


하지만, bash 쉘은 expr를 built-in으로 내장시켰습니다.

쉘스크립트에서 expr 명령어를 사용하면,

/usr/bin/expr를 사용하지 않고 내장된 build-in 명령어를 실행합니다.

build-in 명령이 외부 명령보다 빠릅니다.

외부 명령어는 프로그램 로딩/런칭만으로도 하세월 보내겠죠.

물론, 굳이 외부 명령어를 사용하고 싶다면 절대경로를 지정해서 사용할 수 있습니다.


expr로 연산할 수 있는 내용은 아래 4가지 입니다.

- 산술 : +, -, *, /, %

- 논리 : |(or), &(and)

- 관계 : =, >, >=, <, <=, !=

- 문자열 : ':'(일치 혹은 substitute)


expr도 명령어이기 때문에 연산자와 피연산자는 (space)로 구분되어야 합니다.

아래와 같이 공백을 안주면 5+1을 하나의 문자열로 인식합니다.

$ expr 5+1
5+1

아래와 같이 공백을 주어야 제대로 답이 계산됩니다.

$ expr 5 + 1
6


연산자로 쓰이는 기호 중 일부는 이미 특수문자로 사용되고 있습니다.

특수문자 : '*' all, '&' bg, '|' pipe, '>', '<' redirection, '(', ')' grouping

위의 문자를 연산에 사용하려면 쉘스크립트가 낚아채서 다른 부호로 오해하지 않게,

각 부호 앞뒤로 따옴표로 감싸주거나 부호 앞에 escape 문자인 '\'를 붙입니다.

$ expr 5 '*' 2
10
$ expr 5 "*" 2
10
$ expr 5 \* 2
10


만약 expr에 사용되는 인수들을 통째로 따옴표처리하면,

expr에 연산자 / 피연산자의 구분되어 처리되지 않고 하나의 문자열로 취급됩니다.

$ expr '5 * 3'
5 * 3


'=' 연산은 좌우의 피연산자의 값이 같으면 1(true)을 출력하고 다르면 0(false)을 출력합니다.

여기서 '출력'의 의미는 '리턴값'과 다른 것에 주의합니다.

'출력'은 true의 경우 1이고, false의 경우 0입니다.

하지만 '리턴값'은 true인 상황에서는 0이고 false인 상황에서는 1이지요.

값에서 보면, '출력'과 '리턴값'은 서로 반대이기 때문에 헛갈릴 수밖에 없습니다.

$ a=5
$ expr $a = 5
1
$ echo $?
0
$ expr $a = 4
0
$ echo $?
1
$ expr $a = '5 * 3'
0
$ echo $?
1

이렇게 된 연유는 '리턴값'에 대한 유닉스 고유의 정책때문입니다.

유닉스에서는 에러없이 성공적으로 프로그램이 종료되면 '0'을 리턴합니다.

문제가 있으면 '0' 외에 다른 값을 출력합니다.

따라서 쉘스크립트에서도 '0'인 경우를 '성공'으로 판단합니다.


하지만 일반적인 프로그래밍 언어에서는 '1'은 true이고 '0'은 false입니다.

expr가 출력하는 값은 일반적인 프로그래밍 언어에서 사용하는 방식 그대로입니다.

이 부분은 오랜 시간 규약처럼 정해져서 이제는 바꾸기 힘들겁니다.

이에 대한 이야기는 나중에 할 기회가 또 오겠지요.


$ E="="
$ expr $E = "="
1

위와 같은 경우에는 마치 등호가 세 개 나란히 쓰인 것처럼 보이겠네요.

하지만, 가운데 등호만 등호로 인식되고,

좌우의 등호기호는 피연산자인 문자로 인식됩니다.

쉘 버전에 따라 좌우에 있는 '='도 연산자로 인식하여 오류가 나는 경우도 있습니다.


따라서 연산기호를 사용한 변수를 위해 변수 앞에 문자열 'x'를 붙여서 비교할 수 있습니다.

$ expr x$E = x"="
1

'x' 문자를 붙이면 변수가 null이더라도 제대로 비교가 되지요.


expr 명령어가 리턴값 외에 출력값이 있으니 출력값을 제거하고 싶을 때도 있을겁니다.

그럴 때는 redirection을 이용합니다.

$ a=7
$ expr $a = 7 > /dev/null && echo $a
7

/dev/null로 출력을 보내버리면 더 이상 화면에는 아무 것도 출력하지 않습니다.

하지만, &&가 연달아 붙어있습니다.

expr 명령문 자체가 성공을 의미하는 '0'을 리턴하므로,

&& 뒷편의 명령어도 실행이 됩니다.

echo $a에 의해 최종적으로 a 변수값이 출력되었습니다.


$ expr $a = 8 > /dev/null && echo $a

반면 위와 같이 $a가 7인데 8과 비교를 하였다면, 결과값은 실패를 의미하는 1이 나옵니다.

결과가 1이면 &&의 뒷부분은 실행되지 않습니다.


이제 logical 비교를 수행해보겠습니다.

$ expr "" \| ""
0
$ echo $?
1

null값 두 개를 oring(|) 해봤자 결과는 null입니다.

false를 의미하는 0을 출력하였고, 실패라는 의미의 '1'을 리턴하였습니다.


$ expr "" \| 0
0
$ echo $?
1

null과 0을 oring(|) 해도 결과는 false입니다.


반면 피연산자 중 하나가 1이거나 문자라면 결과는 true가 됩니다.

출력값은 1 혹은 문자가 나옵니다.

$ expr "" \| 1
1
$ echo $?
0
$ expr "" \| "a"
a
$ echo $?
0


$ expr "a" \| "hello"

위의 출력값은 무엇일까요?

"a"에서 이미 oring의 결과가 결정되었습니다.

따라서 출력은 "a"입니다.


and 연산은 피연산자 중에 하나가 null 혹은 0이면 출력은 언제나 '0'이 됩니다.

$ expr '' \& ''
0
$ echo $?
1
$ expr "" \& ""
0
$ echo $?
1
$ expr "" \& 1
0
$ echo $?
1
$ expr "" \& "a"
0
$ echo $?
1

리턴값은 실패를 의미하는 1입니다.


$ expr 1 \& "a"
1
$ expr "a" \& 1
a

양쪽에 값이 있으면 앞쪽에 나온 값이 출력값이 됩니다.

리턴값은 당연히 성공을 의미하는 0입니다.


":" 연산은,

string : 정규표현식

위와 같은 형태로 사용합니다.

string 문자열 내에서 정규표현식에 부합하는 문자열의 길이를 출력합니다.

$ expr story : story
5
$ echo $?
0
$ expr story : storycompiler
0
$ echo $?
1
$ expr storycompiler : story
5
$ echo $?


하지만 정규표현식은 string의 제일 첫 문자부터 일치여부를 가립니다.

$ expr compiler_of_story : story
0

위처럼 string에 story가 있음에도 불구하고,

출력값은 false를 뜻하는 0이 나왔습니다.


$ a="1234abcd"
$ expr $a : [^a-zA-Z]*[a-zA-Z]
5
$ expr $a : [^a-zA-Z]*[a-zA-Z]*
8

위처럼 복잡하게 정규식을 사용하여 정규식에 일치하는 문자개수를 출력할 수 있습니다.

마지막에 '*' 유무에 따라 "1234a" 혹은 "1234abcd"까지 일치하지요.


expr에 괄호를 사용하여 괄호 안에서 매치되는 부분만 출력할 수 있습니다.

$ expr "$a" : '[0-9]*\([a-z]*\)'
abcd
$ expr "$a" : "[0-9]*\([a-z]*\)"
abcd

a변수는 "1234abcd"인 경우,

[0-9]*에 의해 1234가 매치됩니다.

그리고 그 뒤에 괄호에 의해 abcd가 매치가 됩니다.

expr는 괄호 내에서 매치되는 부분이 있으면 그 내용을 출력합니다.

매치된 문자 개수말고 매치된 내용을 출력하게 되지요.


$ expr "$a" : [0-9]*\([a-z]*\)
0

단, 위처럼 ""나 ''로 정규식을 감싸지 않으면 매치된 문자열을 출력하지 않습니다.

따옴표로 감싸진 부분만 스트링으로 인지하여 출력합니다


여태까지 살펴본 expr의 수많은 연산자들 사이에는 우선순위가 있습니다.

총 7단계로 나눌 수 있습니다.

1. 괄호

     괄호는 다른 모든 우선순위를 능가합니다. 어느 언어에서나 마찬가지이죠.

2. string : 정규식

     문자열에서 정규표현식을 부분이 괄호를 제외하고 가장 높습니다.

3. *, /, %

     곰셈, 나눗셈, 나머지 연산

4. +, - 

    덧셈, 뺄셈

5. =, >, >=, <, <=, !=

    관계

6. &

    and연산

7. |

    or 연산


이상과 같습니다.

우선순위가 확실히 기억나지 않는다면 괄호로 감싸주면 되겠지요.


오늘은 여기까지 하겠습니다

그럼 좋은 하루 보내세요~

끝_


* References

http://www.grymoire.com/Unix/Sh.html#uh-84