제어문과 변수까지 살펴보니 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

  1. dn 2016.07.14 17:53

    좋은정보감사합니다!!!ㅠㅠ

  2. 뉴트리노개미 2016.11.06 11:59

    리눅스 쉘 프로그래밍 배우는중에 연산자가 어떤 종류가 있나 해서 들어왔는데 굉장히 자세한 예제를 들어 설명해주셔서 많은 정보를 익히고 갑니다. 감사합니다 ㅎㅎ

쉘스크립트에서 변수를 빼먹으면 안되죠~

어쩌면 가장 먼저 다뤄야했을 내용일지도 모르겠네요.

워낙 두서없이 생각나는 순서로 정리하다보니 뒤로 미뤄졌습니다.

하지만 그렇다고 해서 그 우선순위에서 밀린다는 얘기는 절대로 아닙니다.



변수명은 영문자, 숫자 그리고 '_'(underscore)로만 이뤄집니다.

그 외의 문자는 변수명으로 인식하지 못합니다.

따라서 $FILE.old라는 문장에서 $FILE만이 변수명이 될 수 있습니다.

".old"부터는 "." 때문에 변수명이 될 수 없습니다.

그저 변수명 뒤에 연달아 나온 스트링으로 인식됩니다.


$ touch secret
$ FILE=secret
$ mv $FILE $FILE.old
$ ls secret*
secret.old

위의 예에서는 먼저 secret란 파일을 만든 후,

$FILE 변수를 사용하여 secret.old로 파일이름을 바꿉니다.

변수명과 '.old' 사이에 빈칸이 없어도,

'.'이 변수명의 범주에 들지 않기 때문에 '.' 뒤부터는 자연스레 스트링으로 인식됩니다.

다른 고급언어에서는 이와 같은 상황에서 에러를 출력하지만,

쉘스크립트에서는 이런 모호함도 처리해주네요.


이와는 대조적으로 '.' 대신 '_'를 사용하면 모두 변수명이 됩니다.

$ mv $FILE $FILE_new
mv: `secret' 다음에 대상 파일 명령이 누락

위처럼 $FILE_new는 'FILE_new'라는 이름의 변수일 뿐입니다.

변수에는 아무것도 넣어져 있지 않기 때문에 NULL입니다.

그래서 명령이 누락되었다는 메시지가 나오지요.


굳이 $FILE 변수값에 '_'가 포함된 스트링을 덧붙이고 싶다면,

아래에 명시된 방법 중에 하나를 사용하면 됩니다.

mv $FILE $FILE"_new"
mv $FILE $FILE'_new'
mv $FILE $FILE""_new
mv $FILE $FILE''_new
mv $FILE "$FILE"_new


큰따옴표를 변수명쪽에 붙이든 스트링쪽에 붙이든 결과는 같습니다.

흥미로운건 변수명과 스트링 사이에 따옴표를 넣어도 결과는 같다는 것이겠죠?

왜 그런걸까요?

""는 고급언어처럼 스트링을 나타내는 상징이 아닙니다.

스크립트에서는 ""는 따옴표 안에 있는 요소를 그룹으로 묶는 기능이 있습니다.

따옴표 안으로 넣어 그룹이 되면 그 밖의 요소는 그룹에 속하지 않은 요소가 됩니다.

좌우도 구분지어지겠지요.


mv $FILE $FILE\_new

'\'는 escape 문자처럼 동작하는데요,

변수와 스트링을 구분할 때도 사용됩니다.

변수명 뒤에 오는 '\'는 영문이나 숫자 혹은 '_'가 아니기 때문에 변수명이 될 수 없습니다.

그렇다면 당연히 변수명은 '\' 앞글자까지겠죠.

$ echo "\_str"
\_str
$ echo '\_str'
\_str


일반적으로 큰따옴표나 작은따옴표로 "\_"를 출력하면 역슬래시도 그대로 나옵니다.

왜냐하면 "_"는 escape 없이도 표현할 수 있는 문자이기 때문입니다.

하지만, 따옴표 없이 "\_"를 출력하면, 역슬래시는 없어지고 "_"만 출력됩니다.

비단 '_' 뿐만 아니라 어떤 글자가 와도 역슬래시는 표시되지 않습니다.


쉘단에서 따옴표 없는 \는 다음 문자를 연속되는 문자로 취급되게 해줍니다.

곧 변수명이 치환된 후에 이어서 오는 문자열과 자연스럽게 합쳐지지요.

$ echo \_str
_str
$ echo \+
+
$ echo \-
-
$ echo \a
a


변수명을 보다 확실하게 확정짓는 방법이 있습니다.

변수명 좌우로 괄호 {, }를 치면 됩니다.

괄호영역 내에 있는 이름이 변수명이 됩니다.

mv $FILE ${FILE}_new


이상으로 변수명에 대해 전반적으로 훑어보았습니다.

이제는 변수값을 지정하는 여러가지 방법에 대해 따져보겠습니다.


괄호에는 단지 변수명만 들어가지 않습니다.

$ cat ${NO_VARIABLE? No variable}
bash: NO_VARIABLE:  No variable
$ echo $?
1

위처럼 변수명이 정의되지 않았을때 물음표 뒤의 문장을 출력할 수 있습니다.

대신 에러(1)가 리턴되므로 조건문/반복문에서 제대로 처리해야합니다.


괄호 안에 '-'가 들어가는 경우도 있습니다.

'-'는 변수가 정의되지 않은 경우에만 적용하라는 의미입니다.

$ echo ${NO_VARIABLE-$HOME}
/home/storycompiler
$ echo $?
0

NO_VARIABLE 변수가 사전에 정의되지 않았다면,

$HOME 값이 대입됩니다.


괄호 안에 '+'를 추가할 수도 있습니다.

'+'는 '-'기호와 반대의미를 가집니다.

'-'가 선언되지 않았을때 사용한다면,

'+'는 선언되었을때 사용합니다.

$ echo ${HOME+"home is set"}
home is set
$ echo $?
0

위의 예에서 $HOME변수가 선언되어 있으면 뒤의 문자열을 출력합니다.


괄호 안에 '='를 사용한다면,

'='의 의미답게 변수에 값이 있든 없든 관계없이 '=' 뒤의 값이 대입됩니다.

$ echo ${NO_VARIABLE=variable}
variable
$ echo ${NO_VARIABLE}
variable


정리하자면,

"?"는 정의되어 있지 않으면 뒤의 문자열을 출력하고,

"+"는 정의 되어 있어야 뒤의 문자열을 출력합니다.


"-"는 정의되어 있지 않으면 뒤의 문자열을 대입하고,

"="는 "-"와 동일합니다.

대입과 출력의 차이가 있습니다.


'?', '-', '+', '=' 앞에 ':' 문자를 사용할 수도 있습니다.

':' 문자는 null로 define 된 경우까지 추가하여 각 연산을 강화합니다.


$ A=""
$ echo ${A? "undefined"}

$ echo ${A:? "undefined or null"}
bash: A:  undefined or null


위와 같이 A에 ""를 대입하면, 변수 A는 defined 상태입니다.

따라서 ${A? "undefined"}에서 "undefined" 문자열을 출력하지 않지요.

하지만 ':'를 붙이게 되면, null로 정의된 경우도 검출이 됩니다.

곧, ?는 정의되지 않으면 출력하지만,

:?는 정의되지 않은 것 뿐만 아니라 null로 정의되더라고 출력합니다.


"+"는 정의된 경우에만 문자열을 출력했다면,

":+"는 null이 아닌 값이 정의되어 있는 경우에만 문자열을 출력합니다.


"-"는 정의되어 있지 않으면 문자열을 대입했지만,

":-"는 변수에 값이 정의되지 않았거나 null로 대입된 경우에 뒤의 문자열을 대입합니다.

":="는 ":-"와 동일합니다.


$ echo ${A+"defined"}
defined
$ echo ${A:+"defined"}
(공백)


"+"의 경우, A가 정의되어 있으면 defined를 출력합니다.

":+"의 경우, null로 정의되어 있기 때문에 ":+" 뒤의 문자열을 출력하지 않습니다.


이상으로 변수에 대한 이야기를 마치겠습니다.

그럼 좋은 하루 보내세요~

끝_




* References

http://bash.cyberciti.biz/guide/Rules_for_Naming_variable_name


쉘스크립트 설명에 인용부호를 빼먹을 수는 없지요.

명령 프롬프트에 남길 수 있는 인용부호가 세 종류나 되기 때문에 꼭 짚고 넘어가야 합니다.

작은 따옴표, 큰 따옴표, 역 따옴표.

위의 따옴표 삼형제는 쓰임이 모두 다릅니다.

각각의 인용부호에 대한 정의를 분명하게 확인하지 않으면 낭패볼 일이 있을지도 모릅니다.



화면에 스트링을 출력하기 위해서는 echo 명령어를 이용합니다.

$ echo string
string
$ echo "string"
string

echo에 큰 따옴표를 하든 하지않든 모두 string을 출력해줍니다.


하지만 큰 따옴표를 출력하려면 어떻게 해야할까요?

위의 두 번째 예에서 큰 따옴표가 출력되지 않은 것은 확인했습니다.

큰 따옴표를 출력하기 위해서는 아래 세 가지 중 하나를 택하면 됩니다.

$ echo "\""
"
$ echo \"
"
$ echo '"'
"

역슬래시를 사용하여 큰 따옴표를 출력하거나,

작은 따옴표로 큰 따옴표를 감싸서 출력할 수도 있네요.


그러면 바로 위의 예에서 쓰인 백슬러시는 어떻게 출력해야하는 것일까요?

백슬러시도 위와 유사한 방법으로 출력하면 됩니다.

$ echo "\\"
\
$ echo \\
\
$ echo '\'
\


위의 예에서 알 수 있는 사실이 있습니다.

작은 따옴표로 감싸진 문자열은 변화없이 그대로 출력됩니다.

설사 큰 따옴표 자신이나 백슬래시라 할지라도 문자변환 없이 작은 따옴표 안에 있는 모습 그대로입니다.


이제 작은 따옴표를 출력해보겠습니다.

작은 따옴표는 아래 두 가지 방식으로 출력할 수 있습니다.

$ echo \'     # echo \"와 유사
'
$ echo "'" # echo '"'와 유사하고 echo "\""와도 유사
'

먼저 언급한 큰 따옴표의 출력과 유사하지만 하나 다른 점이 있습니다.

바로 큰 따옴표와 역슬래시 조합이 없네요.

위의 조합으로 출력하면 아래와 같은 결과가 나옵니다.

$ echo "\'"
\'

분명 echo "'"와 "\'" 둘 중 하나를 선택해서 '를 출력하게 결정해야 했겠지요.

그 중에 가장 경제적인 선택을 했을겁니다.

그래서 "'"가 '를 나타내는 것으로 확정했으리라 추정하고 있습니다.


하지만, 제 소견으로 생각하건데,

어쩌면 논리적으로는 "\'"를 선택했어야하지 않았을까란 생각도 듭니다..


작은 따옴표는 작은 따옴표 안에 있는 것을 가급적 그대로 출력합니다.

변수를 작은 따옴표 내에서 출력하면 변수명 그대로 출력이 됩니다.

$ echo '$HOME'
$HOME


이에 반해 변수를 큰 따옴표 안에 넣으면 변수가 실제 값으로 치환된 후 출력됩니다.

$ echo "$HOME"
/home/storycompiler


역 따옴표는 따옴표 안에 있는 명령문의 실행하여 실행결과를 대입합니다.

$ echo The working directory is `pwd`
The working directory is /home/storycompiler
$ echo "The working directory is `pwd`"
The working directory is /home/storycompiler

위처럼 `pwd`의 결과값이 나옵니다.


하지만, 아래의 경우에서는 역따옴표가 제대로 동작하지 않아 의도한 값이 출력되지 않습니다.

$ echo 'The working directory is `pwd`'
The working directory is `pwd`
$ echo 'The working directory is \`pwd\`'
The working directory is \`pwd\`
$ echo "The working directory is \`pwd\`"
The working directory is `pwd`

역따옴표는 역따옴표 자체를 사용해야 의미가 있습니다.

백슬래시로 역따옴표를 막는다면 제대로 동작하지 않겠죠.


간단하게 인용부호를 살펴봤습니다.

그럼 좋은 하루 보내세요~

끝_


* References

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

+ Recent posts