루프란 루프 제어 조건이 참인 동안에 여러 명령어들을 반복적으로 수행하는 코드 블럭입니다.
다음은 기본적인 루프문인데 C의 루프문과는 상당한 차이를 보입니다.
for arg in [list]
do
command...
done
참고: 루프의 각 단계마다 list의 값들이 arg에 들어갑니다.
for arg in "$var1" "$var2" "$var3" ... "$varN" # 루프 1 단계, $arg = $var1 # 루프 2 단계, $arg = $var2 # 루프 3 단계, $arg = $var3 # ... # 루프 N 단계, $arg = $varN # [list]의 인자들은 쿼우팅을 해서 낱말 조각남(word splitting)을 막아 줘야함. |
list 인자에는 와일드 카드가 올 수도 있습니다.
do를 for와 한 줄에 쓴다면 list 뒤에 세미콜론이 있어야 합니다.
for arg in [list] ; do
예 10-1. 간단한 for 루프
#!/bin/bash # 떠돌이별 목록. for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto do echo $planet done echo # 따옴표로 묶인 전체 '목록'은 한 개의 변수를 만들어 냅니다. for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto" do echo $planet done exit 0 |
참고: 각 [list] 항목은 다중 인자를 가질수 있습니다. 이는 인자가 그룹으로 되어 있을 때 유용한데 이런 경우에는 set 명령어(예 11-10 참고)를 써서 [list] 각 항목이 위치 매개변수로 파싱되어 할당되도록 해 주면 됩니다.
예 10-2. 각 [list] 항목이 인자를 두 개씩 갖는 for 문
#!/bin/bash # 떠돌이별 재검토. # 각 떠돌이별의 이름과 해(sun)까지 거리를 한 쌍으로 묶음. for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483" do set -- $planet # "planet" 변수를 파싱해서 위치 매개변수로 세팅. # "--" 를 쓰면 $planet 이 널이거나 대쉬 문자로 시작하는 등의 까다로운 상황을 처리해 줍니다. # 원래의 위치 매개변수는 덮어써지기 때문에 다른 곳에 저장해 놓아야 할지도 모릅니다. # 배열을 써서 해 볼 수 있겠네요. # original_params=("$@") echo "$1 해까지 거리 $2,000,000 마일" done # (S.C. 가 확실한 설명을 더 해 줬습니다.) exit 0 |
for 문의 [list]에 변수가 들어갈 수도 있습니다.
예 10-3. Fileinfo: 변수에 들어 있는 파일 목록에 대해 동작
#!/bin/bash # fileinfo.sh FILES="/usr/sbin/privatepw /usr/sbin/pwck /usr/sbin/go500gw /usr/bin/fakefile /sbin/mkreiserfs /sbin/ypbind" # 여러분이 궁금해하는 파일들 목록. # 더미 파일인 /usr/bin/fakefile 을 그냥 넣었습니다. echo for file in $FILES do if [ ! -e "$file" ] # 파일이 존재하는지 확인. then echo "$file 은 존재하지 않는 파일입니다."; echo continue # 다음 파일을 확인. fi ls -l $file | awk '{ print $9 " 파일 크기: " $5 }' # 2개의 필드를 출력. whatis `basename $file` # 파일에 대한 정보. echo done exit 0 |
[list]에는 파일명 globbing이 올 수도 있습니다. 즉, 파일명 확장을 위한 와일드 카드를 쓸 수 있습니다.
예 10-4. for 문에서 파일 조작하기
#!/bin/bash # list-glob.sh: "globbing"으로 for 루트의 [list] 만들어내기. echo for file in * do ls -l "$file" # $PWD(현재 디렉토리)의 모든 파일을 나열. # 와일드 카드 문자인 "*"는 모든 문자와 일치합니다. 기억나죠? # 하지만 "globbing"에서는 점(dot) 파일은 일치하지 않습니다. # 만약에 패턴이 아무 파일과 일치하지 않는다면 그냥 자기 자신으로 확장되는데, #+ 이걸 피하려면 nullglob 옵션을 주면 됩니다. # (shopt -s nullglob). # S.C.의 지적 사항. done echo; echo for file in [jx]* do rm -f $file # $PWD에서 "j"나 "x"로만 시작하는 모든 파일을 지움. echo "\"$file\" 이 지워졌습니다." done echo exit 0 |
for 문에서 in [list]을 안 써주면 명령어 줄에서 넘어온 인자인 $@에 대해서 동작합니다. 이런 식으로 처리하는 멋진 예제를 보려면 예 A-11를 참고하세요.
예 10-5. in [list]가 빠진 for 문
#!/bin/bash # 인자를 줘서 실행시켜도 보고 안 줘서 실행시켜 본 다음, 어떻게 되는지 보세요. for a do echo -n "$a " done # 'in list' 가 없기 때문에 '$@'에 대해서 동작합니다. # ('$@'는 공백문자를 포함하는 명령어줄 인자 리스트). echo exit 0 |
for 문의 [list]에 명령어 치환를 쓸 수도 있습니다. 예 12-31과 예 10-9, 예 12-29를 참고하세요.
예 10-6. for 문의 [list]에 명령어 치환 쓰기
#!/bin/bash # for 루프의 [list]에 명령어 치환을 쓰기. NUMBERS="9 7 3 8 37.53" for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53 do echo -n "$number " done echo exit 0 |
다음은 [list]를 만들어 낼 때 좀 더 복잡한 명령어 치환을 쓰는 예제입니다.
예 10-7. 이진 파일에 grep 걸기
#!/bin/bash # bin-grep.sh: 이진 파일에서 일치하는 문자열 찾아내기. # "grep"을 이진 파일에 걸기. # "grep -a"라고 해도 비슷합니다. E_BADARGS=65 E_NOFILE=66 if [ $# -ne 2 ] then echo "사용법: `basename $0` string filename" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "\"$2\" 은 존재하지 않는 파일입니다." exit $E_NOFILE fi for word in $( strings "$2" | grep "$1" ) # "strings" 명령어는 이진 파일에 들어 있는 문자열들을 보여주고, # 그 출력을 "grep"에 파이프로 걸어 원하는 문자열을 찾아냅니다. do echo $word done # S.C. 가 위의 for 루프는 다음과 같이 더 간단하게 할 수 있다고 지적해 주었습니다. # strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]' # "./bin-grep.sh mem /bin/ls" 같은 식으로 해서 이 스크립트를 연습해 보세요. exit 0 |
다음도 명령어 치환으로 [list]를 만들어 내는 예제입니다.
예 10-8. 특정 디렉토리의 모든 바이너리 파일에 대해 원저작자(authorship)를 확인 하기
#!/bin/bash # findstring.sh: 주어진 디렉토리의 이진 파일에서 특정한 문자열 찾아내기. directory=/usr/bin/ fstring="Free Software Foundation" # FSF에서 만든 파일에는 어떤 것이 있나 알아볼까요? for file in $( find $directory -type f -name '*' | sort ) do strings -f $file | grep "$fstring" | sed -e "s%$directory%%" # 여기서는 "sed" 표현식의 $directory 가 원래 구분자인 "/"를 포함하고 있기 때문에 # "/" 구분자를 다른 것(%)으로 바꿔줘야 합니다. # 이렇게 하지 않으면 에러 메세지가 납니다(sed -e "s/$directory//" 라고 한 번 해보세요). done exit 0 # 쉬운 연습문제 하나 내겠습니다: # $directory 와 $fstring 을 명령어줄에서 받아 들일 수 있도록 수정해 보세요. |
for 루프의 출력은 다른 명령어로 파이프를 걸 수도 있습니다.
예 10-9. 디렉토리에 들어 있는 심볼릭 링크들을 나열하기
#!/bin/bash # symlinks.sh: 디렉토리의 심볼릭 링크 파일들을 나열하기. ARGS=1 # 명령어줄 인자는 한 개만 필요. if [ $# -ne "$ARGS" ] # 인자가 1 개가 아니면... then directory=`pwd` # 현재 작업 디렉토리 else directory=$1 fi echo "\"$directory\" 디렉토리의 심볼릭 링크들" for file in "$( find $directory -type l )" # -type l = 심볼릭 링크 do echo "$file" done | sort # 파일 목록을 정렬. # Dominik 'Aeneas' Schnitzer 가 지적한대로, #+ $( find $directory -type l ) 을 쿼우트 안 해주면 #+ 파일이름에 공백문자가 들어있는 파일에 대해서는 제대로 동작하지 않습니다. exit 0 |
방금 전의 예제에 약간의 변경만 가하면 루프의 표준출력을 파일로 재지향 시킬 수 있습니다.
예 10-10. 디렉토리에 들어 있는 심볼릭 링크들을 파일로 저장하기
#!/bin/bash # symlinks.sh: 디렉토리에 들어 있는 심볼릭 링크를 나열하기. ARGS=1 # 명령어줄 인자가 한 개 있어야 됩니다. OUTFILE=symlinks.list # 저장할 파일 if [ $# -ne "$ARGS" ] # 인자가 1개가 아니라면... then directory=`pwd` # 현재 작업 디렉토리 else directory=$1 fi echo "\"$directory\" 디렉토리의 심볼릭 링크들" for file in "$( find $directory -type l )" # -type l = 심볼릭 링크 do echo "$file" done | sort > "$OUTFILE" # 루프의 표준 출력이 # ^^^^^^^^^^^^ 저장될 파일로 재지향 됩니다. exit 0 |
이중 소괄호를 쓰면 C 프로그래머들에게 익숙한 형태의 for 루프 문법도 쓸 수 있습니다.
예 10-11. C 형태의 for 루프
#!/bin/bash # 10까지 세는 두 가지 방법. echo # 표준 문법. for a in 1 2 3 4 5 6 7 8 9 10 do echo -n "$a " done echo; echo # +==========================================+ # 이제는 C 형태의 문법을 써서 똑같은 일을 해 보겠습니다. LIMIT=10 for ((a=1; a <= LIMIT ; a++)) # 이중 소괄호와 "$" 없는 "LIMIT". do echo -n "$a " done # 'ksh93' 에서 빌려온 문법. echo; echo # +=========================================================================+ # C 의 "콤마 연산자"를 써서 두 변수를 동시에 증가시켜 보겠습니다. for ((a=1, b=1; a <= LIMIT ; a++, b++)) # 콤마를 쓰면 여러 연산을 함께 할 수 있습니다. do echo -n "$a-$b " done echo; echo exit 0 |
---
자, 이제는 "실제"로 어떻게 쓰이는지 봅시다.
예 10-12. 배치 모드로 efax 사용하기
#!/bin/bash EXPECTED_ARGS=2 E_BADARGS=65 if [ $# -ne $EXPECTED_ARGS ] # 적당한 명령어 줄 인자가 넘어 왔는지 확인. then echo "사용법: `basename $0` phone# text-file" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "$2 는 텍스트 파일이 아닙니다." exit $E_BADARGS fi fax make $2 # 주어진 텍스트 파일에서 팩스 포맷의 파일을 생성. for file in $(ls $2.0*) # 변환된 파일을 이어 붙임. # 변수 목록에서 와일드 카드를 사용. do fil="$fil $file" done efax -d /dev/ttyS3 -o1 -t "T$1" $fil # 동작. # S.C. 가 지적했듯이, # efax -d /dev/ttyS3 -o1 -t "T$1" $2.0* # 라고 하면 for 루프를 안 써도 됩니다만, # 그렇게 좋은 방법은 아닙니다. ^^ exit 0 |
while은 루프 최상단에서 특정 조건을 확인하면서 그 조건이 참일 동안 루프를 계속 돌도록 해 줍니다(종료 상태 0을 리턴합니다).
while [condition]
do
command...
done
for/in 경우처럼 do를 조건 테스트문과 같은 줄에 쓰려면 세미콜론을 써 줘야 합니다.
while [condition] ; do
여기서 설명했던 표준 형태가 아닌 getopts construct같은 특별한 형태의 while 문도 있다는 것에 주의하시기 바랍니다.
예 10-13. 간단한 while 루프
#!/bin/bash var0=0 LIMIT=10 while [ "$var0" -lt "$LIMIT" ] do echo -n "$var0 " # -n 은 뉴라인을 없애줍니다. var0=`expr $var0 + 1` # var0=$(($var0+1)) 이라고 해도 동작합니다. done echo exit 0 |
예 10-14. 다른 while 루프
#!/bin/bash echo while [ "$var1" != "end" ] # while test "$var1" != "end" do # 라고 해도 동작함. echo "변수값을 넣으세요 #1 (끝내려면 end) " read var1 # 'read $var1' 이 아니죠? 왜 그럴까요? echo "변수 #1 = $var1" # "#" 때문에 쿼우트를 해줘야 됩니다. # 종료 조건이 루프 처음에 테스트되기 때문에 'end'도 에코됩니다. echo done exit 0 |
while 문은 다중 조건을 가질 수 있습니다만 오직 마지막 조건이 루프를 끝낼 조건을 결정합니다. 그렇기 때문에 이럴 경우에는 문법이 약간 달라집니다.
예 10-15. 다중 조건 while 루프
#!/bin/bash var1=unset previous=$var1 while echo "이전 변수 = $previous" echo previous=$var1 [ "$var1" != end ] # 바로 전의 "var1"이 무엇이었는지 계속 확인. # "while"에는 4가지 조건이 있지만 오직 마지막 조건이 루프를 제어합니다. # *마지막* 종료 상태가 중요하다는 말씀. do echo "변수값을 넣으세요 #1 (끝내려면 end) " read var1 echo "변수 #1 = $var1" done # 이 스크립트가 어떻게 돌아가는지 알아내 보세요. # 약간 미묘한(tricky) 부분이 있습니다. exit 0 |
for 루프처럼 while도 이중소괄호를 써서 C 형태의 문법을 적용할 수 있습니다(예 9-22 참고).
예 10-16. C 형태의 문법을 쓰는 while 루프
#!/bin/bash # wh-loopc.sh: "while" 루프에서 10까지 세기. LIMIT=10 a=1 while [ "$a" -le $LIMIT ] do echo -n "$a " let "a+=1" done # 아직은 별로 놀랄게 없네요. echo; echo # +=================================================================+ # 이제 똑같은 것을 C 형태의 문법으로 해 봅시다. ((a = 1)) # a=1 # 이중 소괄호에서는 변수를 세팅할 때 C 처럼 빈 칸을 넣어도 됩니다. while (( a <= LIMIT )) # 이중 소괄호, 변수 앞에 "$"가 없네요. do echo -n "$a " ((a += 1)) # let "a+=1" # 역시 되는군요. # 이중 소괄호를 쓰면 C 문법처럼 변수를 증가시킬 수 있군요. done echo # 이제 C 프로그래머도 Bash 를 쓸 때 편안하게 쓸 수 있겠습니다. exit 0 |
참고: while 루프 마지막에 <을 써 표준입력을 파일에서 재지향 받을 수 있습니다.
until은 루프 최상단에서 특정 조건을 확인하면서 그 조건이 거짓일 동안 루프를 계속 돌도록 해 줍니다(while과 반대).
until [condition-is-true]
do
command...
done
주의할 점은 until이 몇몇 프로그래밍 언어에서 비슷한 형태와는 다르게 루프 처음에서 끝내는 조건을 검사한다는 것 입니다.
for/in 경우처럼 do를 조건문과 한 줄에 같이 쓰려면 세미콜론을 적어줘야 합니다.
until [condition-is-true] ; do