본문 바로가기

IT

[Ubuntu/Linux] 쉘스크립트 Flow control - for, case 문법의 모든 것

지난 포스팅으로 제어문 if와 반복문 while, until을 살펴보았습니다.

이번 포스팅에서는 또 하나의 반복문 for 문과 또 하나의 제어문 case 문을 살펴보겠습니다.


for 문과 case 문을 하나로 묶은 이유는 "in" 구문 때문입니다.

"in" 구문 뒤에 오는 내용이 for문 혹은 case문의 연산 혹은 비교대상이 됩니다.


이번 포스팅을 마치면,

쉘 스크립트의 제어 5형제를 전부 다루게 되는군요.



for문은 아래와 같은 문법을 가집니다.

for name in word ... do list done

위에서 name은 장차 변수명으로 사용될 이름을 적어줍니다.

word ...에 입력되는 단어들은 단어 하나 혹은 집합 하나씩 name 변수에 대입됩니다.

집합은 다수의 단어를 큰따옴표로 묶어 하나의 단위로 만듭니다.


간단하게 in 구문에 명시된 값들을 출력하는 스크립트를 작성해보겠습니다.

따로 스크립트 파일을 만들지 않고 쉘에서 바로 작성하였습니다.

'>'는 쉘에서 출력하는 프롬프트입니다.

$ for i in 1 2 3 4 5 6 7 8 9 10
> do
> echo "Number : $i"
> done
Number : 1
Number : 2
Number : 3
Number : 4
Number : 5
Number : 6
Number : 7
Number : 8
Number : 9
Number : 10


in에 단어를 직접 입력하는 대신 변수를 활용할 수도 있습니다.

NUMBERS="1 2 3 4 5 6 7 8 9 10"

위처럼 변수를 먼저 만들어둡니다.

NUMBERS 변수는 쉘이 살아있는 한 계속 사용할 수 있게 됩니다.

쉘에서 나머지 명령을 입력해봅니다. 이번에는 $NUMBERS를 사용하였습니다.

$ for i in $NUMBERS
> do
> echo "Number : $i"
> done
Number : 1
Number : 2
Number : 3
Number : 4
Number : 5
Number : 6
Number : 7
Number : 8
Number : 9
Number : 10


변수를 사용하면 여러 곳에서 사용할 수 있다는 장점이 있습니다.

in 구문 내에서 여러 차례 같은 변수를 사용할 수 있습니다.

$ for i in $NUMBERS $NUMBERS
> do
> echo "Number : $i"
> done
Number : 1
Number : 2
Number : 3
Number : 4
Number : 5
Number : 6
Number : 7
Number : 8
Number : 9
Number : 10
Number : 1
Number : 2
Number : 3
Number : 4
Number : 5
Number : 6
Number : 7
Number : 8
Number : 9
Number : 10

위의 결과를 보면 NUMBERS의 값들이 두 번 연달아 출력된 것을 확인할 수 있습니다.


NUMBERS 전체를 하나의 아규먼트로 관리할 수도 있습니다.

$NUBMERS 앞뒤로 큰따옴표를 추가하면 됩니다.

큰따옴표는 String으로 변환한다는 의미가 아닙니다.

위에서 언급했듯 하나의 아규먼트로 취급한다는 의미입니다.

$ for i in "$NUMBERS"급
> do
> echo "Number : $i"
> done
Number : 1 2 3 4 5 6 7 8 9 10

결과를 보면 NUMBERS 변수의 값이 한 번에 출력된 것을 확인할 수 있습니다.


in 뒤에는 역따옴표 `를 이용하여 명령문의 실행결과를 넣을 수도 있습니다.

역따옴표에 이미 익숙한 분도 있겠지만 그렇지 않은 분들을 위해 부연하자면,

'~'키와 같이 있는 것이 역따옴표 `입니다. 작은 따옴표 '가 아닙니다.

$ for i in `ls`

위처럼 입력하면 ls의 출력 결과물을 변수 i에 입력하게 됩니다.

i 값을 출력하면 ls의 결과물과 동일합니다.

역따옴표와 ls를 활용하면 파일을 대상으로 반복적인 연산을 쉽게 수행할 수 있습니다.


set 명령어로 Positional parameter를 지정하여 for 문을 보다 강력하게 사용할 수 있습니다.

Positional parameter는 굉장히 유용하게 사용되고 있으니 한 번 짚고 넘어가겠습니다.



위의 propositional parameter를 이용하는 예제는 아래와 같습니다.

하나의 집합을 set 명령어에 인자로 주면, 집합 내에 각각의 단어가 propositional parameter가 됩니다.

$ for DOT in "1 10" "2 20" "3 30"
> do
> set $DOT
> echo "($1, $2)"
> done
(1, 10)
(2, 20)
(3, 30)

"1 10"의 집합을 set에 적용하면,

1은 $1로 얻을 수 있는 첫번째 인자가 되고,

10은 $2로 얻을 수 있는 두번째 인자가 되겠죠.

set과 propositional parameter 궁합으로 굉장한 일들을 해낼 수 있습니다.


이번에는 tr 명령어와 for문을 조합해보겠습니다.

$ tr --help
사용법: tr [<옵션>]... <집합1> [<집합2>]
Translate, squeeze, and/or delete characters from standard input,
writing to standard output.

tr 문은 특정 문자를 다른 문자로 치환할 때 사용할 수 있습니다.

/etc/passwd 파일에서 빈칸을 '_'로 치환하도록 하겠습니다.

/etc/passwd는 ':'로 column을 구분하고 있는데요,

description에 ' '(빈칸)이 들어가서 description 자체가 여러 단어로 인지될 수 있습니다.

이걸 방지하기 위해 우선 빈칸을 다른 문자로 치환하겠습니다.

`cat /etc/passwd | tr ' ' '_'`

위의 명령을 통해 passwd의 문자열 중 빈칸이 모두 '_'로 치환됩니다.

그리고 각각의 라인에서 ':'를 빈칸으로 변경하여 각자 propositional parameter에 대입합니다.

$ for i in `cat /etc/passwd | tr ' ' '_'`; do set `echo $i | tr ':' ' '`; echo user : $1, UID : $3, Home : $6; done
user : root, UID : 0, Home : /root
user : daemon, UID : 1, Home : /usr/sbin
user : bin, UID : 2, Home : /bin
user : sys, UID : 3, Home : /dev
user : sync, UID : 4, Home : /bin
...


이번에는 in 구문 뒤에 변수와 집합과 명령문을 한꺼번에 넣어보겠습니다.

변수 하나에 여러 단어가 있으면 각각으로 구분됩니다.

반면, 큰따옴표로 묶인 다수의 변수는 하나의 집합으로 인지됩니다.

역따옴표의 결과도 빈칸마다 단어로 인지되어 각각 i에 대입됩니다.

아래 예제에서 "BEGIN"과 "END"는 문자열로 인지됩니다.

$ A="1 2 3"
$ B="b"
$ C="c"
$ for i in BEGIN $A "$B $C" `cat read.sh` END; do echo $i; done
BEGIN
1
2
3
b c
#!/bin/sh
while
...
END


case 문법은 아래와 같습니다.

case word in esac
case word in pattern ) list;; esac
case word in pattern | pattern ) list;; esac

word에는 주로 변수가 대입되겠지요.

in 뒤에는 문자열 일치여부를 검증할 패턴이 올 수 있습니다.

패턴이 하나라면 "pattern)"을 사용하면 되고,

패턴이 다수라면 '|'(or)를 이용하여 oring할 수 있습니다.

"pattern | patten)"처럼 사용할 수 있습니다.


case를 사용하는 간단한 쉘스크립트를 작성해보았습니다

echo yes or no?
read WORD
case $WORD in
    yes | YES)
        echo yes.
        ;;
    no | NO)
        echo no.
        ;;
esac

yes나 YES인 경우에는 "yes."를 출력하고,

no나 NO인 경우에는 "no."를 출력하는 프로그램입니다.


위의 쉘스크립트를 직접 쉘에 아래처럼 입력해보았습니다.

한 줄로 표기가 되어 헛갈리실 수도 있겠네요.

간혹 쉘 상에서 간단한 일회용 스크립트를 작성할 때 아래처럼 작성하곤 합니다.

$ read WORD; case $WORD in yes|YES) echo yes.;; no|NO) echo no.;; esac
yes
yes.


위의 스크립트는 지정한 문자열이 모두 일치해야 매칭이 됩니다.

모두 대문자이거나 모두 소문자여야만 제대로 동작하게 되지요.

첫글자만 대문자인 경우에는 일치하는 패턴이 없기 때문에 제대로 동작하지 않습니다.

이를 막기 위해 좀 더 유연하게 패턴을 사용할 수 있습니다.

echo yes or no?
read WORD
case $WORD in
    [yY]* )
        echo yes
        ;;
    [nN]* )
        echo no
        ;;
esac

위에서 [yY]는 []괄호 사이에 나열된 문자 중 하나를 아무거나 택한다는 의미입니다.

[]괄호 뒤에 '*'는 그 이후에 무슨 글자가 오든 매칭시키는 기호입니다.

따라서 첫글자가 y 혹은 Y이면 그 뒤에 무슨 글자가 오든 상관없이 매칭이 되지요.


while 문으로 case 문을 덮어 끊임없이 질문을 날리는 프로그램을 만들 수도 있습니다.

특정 패턴인 경우에만 while 문에서 빠져나가도록 break;; 문을 사용하겠습니다.

아래 예제에서는 첫 글자가 y 혹은 Y 혹은 n 혹은 N이면 loop를 빠져나가게 됩니다.

$ while :; do echo "yes or no"; read WORD; case $WORD in [yYnN]*) break;; esac; done
yes or no
y


아래에서는 continue를 사용해보았습니다.

조건에 합치하면 이어지는 명령어를 수행하지 않고,

바로 while 문 초반으로 돌아갑니다.

$ while :; do echo "yes or no"; read WORD; case $WORD in [yn]*) echo "correct"; continue;; esac; echo "wrong"; done
yes or no
yes
correct
yes or no
no
correct
yes or no
hello
wrong


이상으로 간단하게 for문과 case문을 살펴보았습니다.

제어문 5형제를 간단하게 살펴보았는데요,

이는 쉘스크립트의 기본 중에 기본이기 때문에 응용을 위해서는 다른 기능을 더 살펴봐야합니다.

기회가 되면 유용한 기능을 하나씩 살펴보기로 하겠습니다.


그럼 좋은 하루 보내세요~

끝_


* References

http://wiki.bash-hackers.org/scripting/posparams

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