반복문(Loop)이란 무엇인가?
반복문은 프로그램이 특정 구문을 여러 번 반복해서 실행하도록 만드는 구조입니다. 이때 반복 실행되는 구문을 '루프 본문(loop body)'이라고 부릅니다.
루프는 제어 표현식(controlling expression)의 값이 0이 될 때까지 계속 실행되며, 제어 표현식에는 어떤 스칼라(scalar) 데이터 타입이든 사용할 수 있습니다.
셸(shell) 언어 역시 여러 가지 순환(iteration) 또는 반복문을 제공합니다. 이번 글에서는 Bash가 제공하는 반복문들을 예제와 함께 자세히 살펴보겠습니다.
Bash가 지원하는 세 가지 반복문
- for 루프
- while 루프
- until 루프
이 글은 연재 중인 Bash 튜토리얼 시리즈의 일부입니다.
루프는 중첩(nesting)하여 사용할 수 있습니다. 다른 프로그래밍 언어와 마찬가지로 Bash 역시 현재 루프를 빠져나가는 break 문과, 루프의 다음 반복으로 넘어가는 continue 문을 지원합니다.
Bash for 루프 – 첫 번째 형태
for 루프는 일반적으로 반복 횟수가 루프에 들어가기 전에 이미 알려져 있을 때 사용합니다. Bash는 두 가지 형태의 for 루프를 지원하며, 첫 번째 형태는 다음과 같습니다.
for varname in list do commands ##Body of the loop done
위 문법에서 각 요소의 의미는 다음과 같습니다.
- for, in, do, done은 예약어(keyword)입니다.
- list는 항목들의 목록으로 구성된 리스트입니다.
- varname은 임의의 Bash 변수 이름입니다.
이 형태에서 for 문은 리스트에 포함된 각 항목에 대해 한 번씩 본문의 명령을 실행합니다. 루프를 돌 때마다 리스트에서 가져온 현재 항목이 변수 'varname'에 저장되며, 이 변수는 루프 본문 안에서 자유롭게 처리할 수 있습니다. 리스트는 공백으로 구분된 여러 단어를 담고 있는 변수일 수도 있습니다. 만약 for 문에서 리스트를 생략하면, 셸에 전달된 위치 매개변수(positional parameter)가 대신 사용됩니다.
Bash for 루프 예제 1. Zip 파일 전체 압축 해제하기
다음 예제는 루트 디렉터리에서 '*.zip*' 패턴과 일치하는 파일 목록을 찾아, zip 파일이 있는 같은 위치에 새 디렉터리를 생성하고 그곳에 zip 파일의 내용을 압축 해제합니다.
# cat zip_unzip.sh
#! /bin/bash
# Find files which has .zip
for file in `find /root -name "*.zip*" -type f`
do
# Skip the extension .zip
dirname=`echo ${file} | awk -F'.' '{print $1}'`
# Create the directory
mkdir $dirname
# Copy the zip file
cp ${file} ${dirname}
cd $dirname
# Unzip the zip file from newly created directory
unzip ${dirname}/$(echo ${file##/*/})
done
- 이 예제에서 find 명령은 파일 목록을 반환하며, 각 파일이 루프를 통해 하나씩 처리됩니다.
- 각 항목에 대해 zip 파일 이름으로 디렉터리를 생성하고, zip 파일을 새로 만든 디렉터리로 복사한 뒤 그곳에서 압축을 해제합니다.
- echo ${file##/*/} 구문은 경로를 제외한 파일 이름만 추출해 줍니다.
# ./zip_unzip.sh Archive: /root/test2/test2.zip extracting: t1/p extracting: t1/q extracting: t1/r extracting: t1/s extracting: t1/t extracting: t1/u extracting: t1/v Archive: /root/test1/test1.zip extracting: t/a extracting: t/b extracting: t/c extracting: t/d extracting: t/e
Bash 루프와 마찬가지로 Awk에서도 for 루프와 while 루프를 제공합니다. 자세한 내용은 이전에 다룬 Awk While 및 For Loop 관련 글을 참고하세요.
Bash for 루프 – 두 번째 형태 (C 스타일)
두 번째 형태의 for 루프는 C 언어의 for 루프와 유사하며, 초기화(initialization), 조건(condition), 증감(updation)의 세 가지 표현식을 사용합니다.
for (( expr1; expr2; expr3 ))
do
commands
done
- 위 Bash for 명령 문법에서, 첫 번째 반복이 시작되기 전에 expr1이 평가됩니다. 주로 루프에서 사용할 변수를 초기화하는 용도로 쓰입니다.
- do와 done 사이의 모든 구문은 expr2가 참(TRUE)인 동안 반복 실행됩니다.
- 루프가 한 번 돌 때마다 expr3이 평가됩니다. 주로 루프 카운터를 증가시키는 용도로 사용됩니다.
다음 예제는 n개의 난수(random number)를 생성합니다.
Bash for 루프 예제 2. n개의 난수 생성하기
$ cat random.sh
#! /bin/bash
echo -e "How many random numbers you want to generate"
read max
for (( start = 1; start <= $max; start++ ))
do
echo -e $RANDOM
done
$ ./random.sh
How many random numbers you want to generate
5
6119
27200
1998
12097
9181
위 코드에서 for 루프는 최대(max) 횟수만큼 난수를 생성합니다. RANDOM은 호출할 때마다 임의의 정수를 반환하는 Bash 내장 기능입니다.
Bash while 루프
셸 프로그래밍 언어가 제공하는 또 다른 반복문은 while 문입니다.
Syntax: while expression do commands done
위 while 루프 문법에서:
- while, do, done은 예약어입니다.
- expression은 스칼라 값을 반환하는 임의의 표현식입니다.
- while 문은 주어진 조건 표현식이 참(true)인 동안 코드 블록을 반복 실행합니다.
Bash while 루프 예제 3. 파일에 내용 쓰기
다음 예제는 표준 출력(stdout)에서 입력받은 데이터를 파일에 기록합니다.
$ cat writefile.sh #! /bin/bash echo -e "Enter absolute path of the file name you want to create" read file while read line do echo $line >> $file done $ sh writefile.sh Enter absolute path of the file name you want to create /tmp/a while for until $ cat /tmp/a while for until
위 예제는 사용자로부터 파일 이름을 입력받고, 표준 입력(stdin)으로부터 데이터를 한 줄씩 읽어 해당 파일에 덧붙여 씁니다. EOF(파일 끝)가 입력되면 read가 실패하므로, 그 지점에서 루프가 종료됩니다.
만약 Bash 스크립트를 많이 작성한다면, 이전에 소개한 것처럼 Vim bash-support 플러그인을 활용해 Vim 에디터를 Bash IDE처럼 사용할 수도 있습니다.
Bash while 루프 예제 4. 파일 내용 읽기
앞선 예제에서는 표준 출력의 데이터를 읽어 파일에 기록했다면, 이번 예제는 파일의 내용을 읽어 표준 출력으로 내보냅니다.
$ cat read.sh #! /bin/bash echo -e "Enter absolute path of the file name you want to read" read file exec <$file # redirects stdin to a file while read line do echo $line done $ ./read.sh Enter absolute path of the file name you want to read /tmp/a while for until
이 예제는 읽을 파일 이름을 받은 후 exec을 사용해 표준 입력(stdin)을 파일로 리디렉션합니다. 이 시점부터 모든 표준 입력은 키보드가 아닌 해당 파일에서 들어옵니다. read 명령은 stdin에서 한 줄씩 읽으므로, while 루프는 EOF가 발생할 때까지 stdin을 계속 읽습니다.
Bash until 루프
until 문은 문법과 기능 면에서 while 문과 매우 유사합니다. 두 문법의 유일한 차이점은, until 문은 조건 표현식이 거짓(false)인 동안 코드 블록을 실행하고, while 문은 조건 표현식이 참(true)인 동안 코드 블록을 실행한다는 점입니다.
syntax: until expression do commands #body of the loop done
위 Bash until 문법에서 until, do, done은 예약어이며, expression에는 임의의 조건 표현식이 들어갑니다.
Bash until 루프 예제 5. 로그 파일 모니터링
이 예제는 로그 파일의 크기를 감시하다가, 파일 크기가 2000바이트에 도달하면 해당 로그 파일의 복사본을 만듭니다.
$ cat monitor.sh
file=/tmp/logfile
until [ $(ls -l $file | awk '{print $5}') -gt 2000 ]
do
echo "Sleeping for next 5 seconds"
sleep 5
done
date=`date +%s`
cp $file "$file-"$date.bak
$ ./monitor.sh
Sleeping for next 5 seconds
Sleeping for next 5 seconds
$ ls -l /tmp/logfile*
-rw-r--r-- 1 sss sss 2010 Jun 24 12:29 logfile
-rw-r--r-- 1 sss sss 2005 Jun 24 16:09 logfile-1277474574.bak
until 문은 조건이 참이 될 때까지 루프 본문을 계속 실행합니다. 이 예제에서 조건은 '파일 크기가 2000바이트보다 큰 경우'이므로, 파일이 2000바이트에 도달하는 순간 복사가 수행됩니다.
참고로, 이전에 소개한 Bash 배열(Array) 예제도 함께 살펴보시면 도움이 됩니다.
Bash until 루프 예제 6. 서버가 기동될 때까지 대기하기
이 예제는 특정 머신에 SSH로 접속하기 전에, 해당 머신이 기동될 때까지 기다리는 용도로 사용됩니다. until 루프는 ping이 응답을 반환할 때만 종료됩니다.
$ cat mac_wait.sh
#! /bin/bash
read -p "Enter IP Address:" ipadd
echo $ipadd
until ping -c 1 $ipadd
do
sleep 60;
done
ssh $ipadd
$./mac_wait.sh
Enter IP Address:192.143.2.10
PING 192.143.2.10 (192.143.2.10) 56(84) bytes of data.
--- 192.143.2.10 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
PING 192.143.2.10 (192.143.2.10) 56(84) bytes of data.
64 bytes from 192.143.2.10: icmp_seq=1 ttl=64 time=0.059 ms
--- 192.143.2.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.059/0.059/0.059/0.000 ms
The authenticity of host '192.143.2.10 (192.143.2.10)' can't be established.
Are you sure you want to continue connecting (yes/no)? yes
until 루프는 특정 이벤트가 발생할 때까지 대기하는 방법으로서 커맨드 라인에서 매우 유용하게 활용됩니다.