Compound Commands
프로그래밍 언어에서 expression 은 실행됐을 때 value 를 반환합니다.
그래서 변수에 값을 대입할 수 있고 &&
, ||
연산자로 연결해 사용할 수도 있습니다.
그 외에 if else, for, while 같은 문은 따로 값이 반환되는 것이 아니고
코드 실행의 역할을 하기 때문에 statement 라고 하고 expression 처럼 사용될 수 없습니다.
이런 점에서 본다면 shell 의 command line 은
&&
, ||
로 연결할 수도 있고 $( )
로 출력을 변수에 대입할 수도 있기 때문에
하나의 expression 으로 볼 수 있습니다.
또한 shell 에서는 if, case, for, while 문도 마지막에 실행된 명령의 종료 상태 값이
반환되기 때문에 하나의 command 처럼 사용될 수 있습니다.
뒤에서 알아보겠지만 compound commands 는 일반 command 와 동일하게 파이프나 redirection
을 붙여 사용할 수도 있습니다.
# if 문을 || 연산자와 연결해 사용할 수 있다.
# if 문에서 마지막으로 실행된 false 명령의 종료 상태 값이 사용된다.
$ if test 1 = 1; then false; fi || echo 123
123
# cd 명령이 성공할 경우 뒤의 while 문이 실행된다.
$ cd /mnt/backup && while true; do echo 123; break; done
123
# for 문에서 마지막으로 실행된 명령은 true 명령이므로 echo 123 이 실행된다.
$ for name in foo; do true; done && echo 123
123
# if 문의 출력을 var 변수에 대입할 수 있다.
$ var=$( if test 1 = 1; then echo 123; fi )
$ echo $var
123
shell 에서 명령들을 구성할 수 있는 방법을 분류해 보면 다음과 같습니다
1 . 한줄에 하나씩
command1
command2
command3
...
2 . ;
메타문자를 이용하면 한줄에 여러 명령을 놓을 수 있다
command1; command2; command3 ...
여기서 ;
메타문자는 newline 과 같은 역할을 합니다. 1, 2 번은 각 명령들이 순서대로 실행이 됩니다. command1 이 종료돼야 그다음에 command2 가 실행되고 command2 가 실행을 종료해야 다음에 command3 이 실행됩니다.
3 . 파이프를 이용해 여러명령을 동시에 실행
command1 | command2 | command3
파이프에 연결된 명령들은 그룹으로 하나의 명령처럼 동시에 실행 됩니다. command1 의 stdout 출력이 command2 의 stdin 입력으로 들어가고 command2 의 stdout 출력이 command3 의 stdin 입력으로 들어가고 최종적으로 command3 의 stdout 출력이 터미널로 표시됩니다.
4 . &&, || 메타문자를 이용한 간단한 조건부 실행
command1 && command2
&&
메타문자는 command1 의 실행이 정상 종료하면 command2 를 실행하고
command1 이 오류로 종료할 경우는 command2 를 실행하지 않습니다.
# command1 && command2 는 다음과 같은것 입니다.
if command1; then
command2
fi
command1 || command2
||
메타문자는 command1 이 오류로 종료할 경우 command2 를 실행합니다.
command1 이 정상 종료하면 command2 는 실행되지 않습니다.
# command1 || command2 는 다음과 같은것 입니다.
if ! command1; then
command2
fi
# 시스템에 ifconfig 명령이 존재하지 않을경우 ip 명령을 사용하게 된다.
ifconfig 2> /dev/null || ip link show up
A && {
B && {
echo "A and B both passed"
} || {
echo "A passed, B failed"
}
} || echo "A failed"
5 . { ... ;} , ( ... ) 을 이용한 명령 grouping
여러 명령들을 하나의 그룹으로 만들어 사용할 수 있습니다. 대표적인게 function 인데요.
이렇게 그룹을 만들면 안에 있는 명령들이 실행시에 같은 context 를 가집니다.
( 예를 들면 { cmd1; cmd2; cmd3 ;} 2> /dev/null
와 같이 실행하면 { }
에 속한
모든 명령들의 stderr 출력이 /dev/null 로 전달됩니다. )
6 . Shell keyword 를 이용한 복합 명령 구성
위에서 알아본 방법만 가지고는 실용적인 스크립트를 구성할 수 없습니다. 그래서 shell 에서는 프로그래밍 언어에서처럼 조건분기 및 반복기능을 구성할수 있게 keyword 를 제공합니다.
Compound Commands
compgen -k | column
명령으로 볼 수 있는 대부분의 shell 키워드들이 복합 명령 구성을 하는데 사용됩니다.
이 키워드를 이용하면 shell 에서도 if else 문과 for, while 문을 만들 수가 있습니다.
키워드는 명령 이름이 위치하는 곳에서 사용되며 인수 부분에서 사용될 경우 키워드로 기능하지 않습니다.
이 키워드들은 사용방법에 있어 다음과 같은 특징이 있습니다.
키워드로 시작해서 키워드로 끝난다.
테이블을 보면 loop 문은 전부 done 키워드로 끝나는걸 알 수 있습니다.
구분 | 구성 |
---|---|
if 문 | if ... fi |
case 문 | case ... esac |
select 문 | select ... done |
while 문 | while ... done |
until 문 | until ... done |
for 문 | for ... done |
원래 unix 초기에는 이와 같은 프로그래밍 언어에서나 볼 수 있는 언어 구성이 없었습니다.
if
가 명령으로 하나 존재하는 정도였는데요.
참고로 쉘 스크립트를 처음 접하시는 분들은 if ... fi
, case ... esac
와 같은 구문이 생소할 수 있는데 이와 같은 구성은 ALGOL 68 언어 에서 찾아볼 수 있습니다.
bash
( bourne again shell ) 는 sh
( bourne shell ) 의 업그레이드 버전이라고 할 수 있는데
sh
을 만들었던 steve bourne 이라는 사람이 당시 ALGOL68 언어에 관여했다고 합니다.
열고 닫는 키워드에 redirection, |, & 를 붙여 사용할 수 있다.
열고 닫는 키워드에 redirection, 파이프, & 를 붙이면 안에 있는 전체 명령에 적용됩니다.
command1 | if command2; then # command1 명령의 출력값이
. . . # if 문 안에서 실행되는 명령들의 stdin 으로 연결되고
fi > outfile # stdout 출력은 outfile 로 쓰여진다.
------------------------------
command1 | case $var in # command1 명령의 출력값이
. . . # case 문 안에서 실행되는 명령들의 stdin 으로 연결되고
esac > outfile # stdout 출력은 outfile 로 쓰여진다.
------------------------------
command1 | while read line; do
. . .
done >&2 # do ~ done 에서 실행되는 명령들의 stdout 출력을 stderr 로 redirect.
--------------------------------------------------------------
if command1; then
command2 | sed 's/aaa/bbb/g' | sed '/^#define /d' > outfile # 중복코드
else
command2 | sed 's/xxx/yyy/g' | sed '/^#define /d' > outfile # 중복코드
fi
if command1; then
command2 | sed 's/aaa/bbb/g' # 파이프 명령 라인에서 사용되는 중복되는 코드를
else
command2 | sed 's/xxx/yyy/g'
fi |
sed '/^#define /d' > outfile # fi | 를 이용해 밖으로 뺄 수 있다.
----------------------------------------------------------------------------
$gcc -g $comp -o $tmpfile "$file" "$@" && # $gcc 명령이 성공하면 &&
if test -n "$func"; then # test 조건에 따라
$objdump -wCS $tmpfile | sed -n '/....../p' # (1) 번 명령문 이나
else
$objdump -wCS $tmpfile # (2) 번 명령문을 실행.
fi
if test -n "$llvm"; then # test 조건에 따라
objdump=llvm-objdump
clang -g -c -o $tmpfile "$file" "$@" # (1) 번 명령문 이나
else
$gcc -g $comp -o $tmpfile "$file" "$@" # (2) 번 명령문을 실행하고
fi && # 실행이 성공할 경우 fi &&
if test -n "$func"; then # test 조건에 따라
$objdump -wCS $tmpfile | sed -n '/....../p' # (3) 번 명령문 이나
else
$objdump -wCS $tmpfile # (4) 번 명령문을 실행.
fi
종료 상태 값
compound command 는 테스트를 통과하여 진입하지 못했을 경우 기본적으로
종료 상태 값이 모두 0
입니다.
그 외는 각 해당 블록에서 마지막으로 실행된 명령의 종료 상태 값이 사용됩니다.
테스트를 통과하여 진입하지 못했을 때는 종료 상태 값이 0
이다
$ if test 1 = 0; then false; fi
$ echo $?
0
$ while test 1 = 0; do false; done
$ echo $?
0
$ set --
$ for i; do false; done
$ echo $?
0
$ AA=100
$ case $AA in (200) false; esac
$ echo $?
0
진입에 성공하였을 경우는 마지막에 실행된 명령의 종료 상태 값이 사용됩니다.
반복문이
break
명령에 의해 종료될 경우는 종료 상태 값은0
이 됩니다.
$ if test 1 = 1; then false; fi
$ echo $?
1
# test 명령의 "unary operator expected" 오류의 종료 상태 값은 2 이지만 false 명령의 값이 사용된다.
$ AA=100
$ while test $AA = 100; do AA=; false; done
bash: test: =: unary operator expected
$ echo $?
1 # false 의 종료상태값
$ while test 100 = 100; do false; break; done # 반복문이 break 에의해 종료될 경우
$ echo $? # 종료 상태 값은 0 이다.
0
$ set -- 100
$ for ARG; do false; done
$ echo $?
1
$ AA=200
$ case $AA in (200) false; esac
$ echo $?
1
예제 )
다음 예제의 경우 num 변수 값이 100 일 경우는 if 블록에 진입하지 못하므로
블록 기본값인 0
이 적용되어 바로 run 함수가 실행됩니다.
그 외의 값에 대해서는 if 블록에 진입이 되어 check 함수가 실행되고
사용자에게 yes, no 를 묻게 됩니다.
yes 를 선택했을 경우 run 함수가 실행되고 no 일 경우는 run 함수가 실행되지 않게 됩니다.
run() { ... ;}
check() {
echo -n "do you want proceed (y/n)? "
while read ans; do
echo
case $ans in
[Yy]) return 0 ;;
[Nn]) return 1 ;;
esac
done
}
num=$( ... )
if [ "$num" -ne 100 ]; then check; fi && run
--------------------------------------------
# 위 마지막 문장은 다음과 같은것 입니다.
if [ "$num" -ne 100 ]; then
check && run
else
run
fi
Conditional Constructs
if
if test-commands; then
consequent-commands
[ elif more-test-commands; then
more-consequents ]
[ else alternate-consequents ]
fi
- if 문은
if
,elif
,else
,fi
그리고then
키워드를 사용합니다. - 키워드 다음에 명령이 오는 순서입니다.
if
다음에 명령;then
다음에 명령; ... 마지막엔fi
로 닫습니다. if
나elif
다음에 오는 명령은[ ]
,[[ ]]
을 사용하는 것 외에도 어떤 명령이나 group 도 사용할 수 있습니다.!
키워드를 이용하면 logical NOT 연산을 할 수 있습니다.
if ! mount /mnt/backup &> /dev/null; then
echo "FATAL: mount backup failed" >&2
exit 1
fi
-----------------------------------------
if grep -q '^2014-07-20' data.txt; then
...
fi
if
문에 else
가 사용되지 않으면 다음 두 명령문은 실질적으로 같게 됩니다.
if test 1 = 1; then test 1 = 1 && {
echo "yes" echo "yes"
fi }
-----------------------------------------------------
if ! test 1 = 2; then ! test 1 = 2 && {
echo "yes" echo "yes"
fi }
아래 두 명령문은 동일한 기능을 하는 것처럼 보이는데요.
[ 1 = 1 ]
가 참이면 command1 이 실행되고 거짓이면 command2 가 실행됩니다.
하지만 조금 차이가 있습니다.
두 번째의 경우에는 [ 1 = 1 ]
가 참이어서 command1 이 실행될 경우
만약에 command1 이 오류로 종료하게 되면 command2 도 실행되게 됩니다.
if [ 1 = 1 ]; then # [ 1 = 1 ] 가 참이고 command1 이 오류로
command1 ... # 종료하면 command2 도 실행이 된다.
else
command2 ... [ 1 = 1 ] && command1 ... || command2 ...
fi
------------------------------------------------
$ AA=100
$ if [ 1 = 1 ]; then AA=`date -@`; else AA=300; fi
date: invalid option -- '@'
. . .
$ echo $AA
$
$ [ 1 = 1 ] && AA=`date -@` || AA=300
date: invalid option -- '@'
. . .
$ echo $AA
300
case
case word in
[ [ ( ] pattern [ | pattern ]...) command-list ;; ]...
esac
shell 에서 제일 유용한 기능중 하나가 case 문이 아닐까 생각하는데요. 각 case 를 패턴을 이용하여 매칭할수 있다는 것은 아주 편리한 기능이라고 생각합니다. case 문에서 사용된 pattern 들은 위에서 아래로 쓰여진 순서에 따라 매칭을 하며 처음 매칭된 패턴이 실행이 되고 이후에 중복되는 매칭에 대해서는 실행이 되지 않습니다.
- case 문은
case
,in
,esac
키워드와 각 case 를 나타내는pattern)
or(pattern)
을 사용합니다. - pattern) 의 종료 문자는
;;
를 사용하고 패턴에 사용되는 glob 문자를 일반 문자로 사용하려면 escape 하거나 quote 해야합니다. - pattern) 에서는
|
을 구분자로 하여 여러개의 값을 사용할 수 있는데 이때 값의 양쪽 끝에 있는 공백은 사용되지 않으므로 확장패턴 에서처럼 서로 붙여쓰지 않아도 됩니다.|
구분자를 포함하는 변수를 pattern 에 직접 사용할수는 없는데 이때는@(...)
확장패턴을 사용하면 가능합니다. - word, pattern 에 공백을 포함하려면 quote 을 하거나
foo\ bar
와같이 escape 하면됩니다. - word, pattern 에서 변수나 명령치환을 사용할 경우
[[ ]]
에서 처럼 단어분리나 globbing 이 발생하지 않으므로 quote 하지 않아도 됩니다. - word 에
$@
,$*
또는 array 가 사용될 경우 전체 원소가 하나의 값으로 사용됩니다. *)
는 모든 매칭을 뜻하므로 default 값으로 사용할 수 있습니다.- 대, 소문자 구분없이 매칭하려면
case ${foo,,} in ...
매개변수 확장을 이용하거나shopt -s nocasematch
옵션을 이용할 수 있습니다.
read -p "Enter the name of an animal: " ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
horse | dog | cat)
echo -n 4
;;
man | kangaroo)
echo -n 2
;;
*)
echo -n "an unknown number of"
;;
esac
echo " legs."
-----------------------------------------------
DIALOG_OK=0
DIALOG_CANCEL=1
DIALOG_HELP=2
return_value=$DIALOG_CANCEL
case $return_value in
$DIALOG_OK)
echo "OK pressed.";;
$DIALOG_CANCEL)
echo "Cancel pressed.";;
$DIALOG_HELP)
echo "Help pressed.";;
esac
--------------------------------------------------------------
# 패턴에 사용되는 glob 문자를 일반 문자로 사용하려면 quote 하거나 escape 합니다.
case $var in case $var in
\?) ... '?') ...
\*) ... '*') ...
\|) ... '|') ...
esac esac
----------------------------------------------
pat="foo|bar|zoo"
val=bar
case $val in
$pat) echo yes ;; # '|' 구분자를 포함하는 변수를 패턴에 직접 사용할수는 없습니다.
esac
case $val in
@($pat)) echo yes ;; # 이때는 @(...) 확장패턴을 사용해야 합니다.
esac # shopt -s extglob
yes
------------------------------------
arr=(11 22 33 44 55)
case ${arr[@]} in # array 는 전체 원소가 하나의 값으로 사용됩니다.
*"22 33"*) echo yes;;
esac
실행결과: yes
sh
에서는 변수값을 비교할 때 test 명령의 경우 문자 그대로 스트링 매칭을 하므로
패턴 매칭을 할 수가 없는데 case 문을 활용하면 가능합니다.
sh$ AA=y # if then ... 에 해당
sh$ case $AA in ([Yy]) echo YES; esac
YES
sh$ AA=z # if then ... else ... 에 해당
sh$ case $AA in ([Yy]) echo YES;; (*) echo NO; esac
NO
bash 의 경우 pattern) 종료 문자로 ;;
대신에 ;&
( fall-through ) 와 ;;&
( next-matching )
을 사용할 수 있습니다.
;&
는 다음 패턴이 매칭이 되던 상관없이 무조건 실행하고
;;&
는 매칭이 되는 다음 패턴을 찾아 실행합니다.
따라서 ;;&
를 사용할 땐 디폴트 매칭인 *)
를 사용하기 어렵겠죠 ( 항상 매칭이 되므로 )
;;&
( next-matching ) 활용 예는 여기 를 참고하세요
#!/bin/bash #!/bin/bash
case AAA in case ACD in
AAA) *A*)
echo AAA echo AAA
;& # fall-through ;;& # next-matching
BBB) *X*)
echo BBB echo XXX
;; ;;&
CCC) *C*)
echo CCC echo CCC
;; ;;& # next-matching
*) *D*)
echo DEFAULT echo DDD
esac esac
---------------- -------------------
# 실행 결과 # 실행 결과
AAA AAA
BBB CCC
DDD
select
select name [ in words ...]
do
commands
done
- select 문은
select
,in
,do
,done
키워드를 사용합니다. PS3
변수는 프롬프트를 설정하는데 사용됩니다.REPLY
변수에는 입력한 값이 저장됩니다.in words
부분을 생략하면in "$@"
와 같게 됩니다.- 값을 입력하지 않고 enter 를 하면 다시 목록을 표시합니다.
- Ctrl-d 는 select loop 를 종료하고 다음 명령으로 진행합니다.
- select 는 반복해서 입력을 받으므로 선택을 완료하고 다음으로 진행하기 위해서는 break 명령으로 종료해야 합니다.
#!/bin/bash
PS3=$'\n'"[ exit \"x\" ] 번호입력: "
while true; do
clear
echo "번호를 선택해 주세요."
echo
AA=( "horse" "dog" "cat" "man" "kangaroo" )
select ANIMAL in "${AA[@]}"
do
case $ANIMAL in
horse | dog | cat)
number_of_legs=4 ;;
man | kangaroo)
number_of_legs=2 ;;
*)
[ "$REPLY" = "x" ] && break 2
echo -e "\nNot available\n"
read -n1; clear; break
esac
echo
echo "You picked number $REPLY"
echo "The $ANIMAL has $number_of_legs legs."
echo
read -n1; break
done # select
done # while
echo
echo "종료되었습니다."
--------------------------------------
번호를 선택해 주세요.
1) horse
2) dog
3) cat
4) man
5) kangaroo
[ exit "x" ] 번호입력: 2
You picked number 2
The dog has 4 legs.
Looping Constructs
shell 에서도 프로그래밍 언어에서처럼 반복문을 제공합니다. 하지만 차이가 있는데요. shell 은 명령을 다루기 때문에 만약에 반복을 10,000 번을 하게 되면 명령 실행을 위해 OS 가 프로세스를 적어도 10,000 번을 생성해야 됩니다. 따라서 프로그래밍 언어에서 사용하는 loop 문과는 속도 면에서 큰 차이가 있게 됩니다. 다음은 20 개의 random 문자를 갖는 스트링 10,000 개를 /dev/urandom 로 부터 추출하여 파일에 저장하는 작업인데 shell 의 반복문과 awk 단일 프로세스 에서 반복문의 실행 시간을 비교해 보면 큰 차이가 나는 것을 볼 수 있습니다.
# shell 에서의 loop 는 10,000 번 head 프로세스가 생성돼야 한다.
$ time tr -dc '[:graph:]' < /dev/urandom |
for (( i=0; i<10000; i++)) do head -c 20; echo; done > x1
real 0m19.095s # 약 20 초
user 0m11.640s
sys 0m8.586s
# awk 단일 프로세스 에서의 loop
$ time tr -dc '[:graph:]' < /dev/urandom |
awk -v RS='.{20}' '{ print RT } NR==10000{ exit }' > x2
real 0m0.017s # 1 초도 안 걸린다.
user 0m0.018s
sys 0m0.012s
$ ls -l x1 x2
-rw-rw-r-- 1 mug896 mug896 210000 2020-04-08 13:17 x1
-rw-rw-r-- 1 mug896 mug896 210000 2020-04-08 13:17 x2
다음은 /usr/bin 디렉토리 에서 각각의 파일 사이즈를 stat 명령으로 구해 총합을 계산하는 예입니다.
# find 명령의 {} \; 는 파일 개수만큼 stat 명령이 실행돼야 한다.
$ time find /usr/bin -type f -exec stat -c %s {} \; | awk '{sum+=$1} END{ print sum}'
1296011570
real 0m5.523s # 약 5 초
user 0m3.043s
sys 0m2.680s
# find 명령의 {} + 는 stat 명령이 한번만 실행된다.
$ time find /usr/bin -type f -exec stat -c %s {} + | awk '{sum+=$1} END{ print sum}'
1296011570
real 0m0.024s # 1 초도 안 걸린다.
user 0m0.017s
sys 0m0.015s
while
while test-commands
do
consequent-commands
done
- while 문은
while
,do
,done
키워드를 사용합니다. - 테스트되는 명령이 정상 종료하면 계속해서 반복합니다.
while 과 read 명령을 이용해 파일을 읽어들일 경우 아래와 같이 파일을 read 명령에 연결하면 안됩니다.
왜냐하면 read -r line < infile
명령이 실행이 완료되면 infile 과 연결이 close 되기 때문입니다.
그래서 다음 반복때 다시 파일을 open 해서 읽어 들이게 되므로 계속해서 첫 라인만 표시되게 됩니다.
while read -r line < infile
do
echo "$line"
done
########## output ##########
Fred:apples:20:June:4
Fred:apples:20:June:4
Fred:apples:20:June:4
...
...
그러므로 파일을 read 명령이 아니라 while 이나 done 키워드에 연결 시켜야 합니다.
done 키워드에 파일을 연결하면 while 문이 종료될때까지 open 되어 사용되며 라인을 읽어들일때 마다 파일 포지션이 다음 라인으로 이동합니다.
while read -r line
do
echo "$line"
done < infile
while 키워드에 |
파이프로 연결한 경우
cat infile | while read -r line
do
echo "$line"
done
until
until test-commands
do
consequent-commands
done
- until 문은
until
,do
,done
키워드를 사용합니다. - 테스트되는 명령이 오류로 종료하면 계속해서 반복합니다.
(until ...
은while ! ...
과 같습니다. )
read -p "Enter Hostname: " hostname
until ping -q -c 1 "$hostname" &> /dev/null
do
sleep 60;
done
curl -O "$hostname/mydata.txt"
for
for name [ in words...]
do
commands
done
- for 문은
for
,in
,do
,done
키워드를 사용합니다. - words 개수만큼 반복하고 매 반복 때마다 name 값이 설정됩니다.
in words
부분을 생략하면in "$@"
와 같게 됩니다.
$ for v in 100 200 300 $ for v in 1.{0..5}
do do
echo $v echo $v
done done
100 1.0
200 1.1
300 1.2
-------------------------- 1.3
1.4
$ set -f; IFS=$'\n' 1.5
$ for file in $(find -type f)
do
echo "$file"
done
./WriteObject.java
./WriteObject.class
./ReadObject.java
./2013-03-19 154412.csv
./ReadObject.class
./쉘 스크립트 테스팅.txt
$ set +f; IFS=$' \t\n'
for (( i = 0; i < 10; i++ )) 를 할 수 있다!
프로그래밍 언어에서 for 문처럼 사용할 수 있습니다.
# 형식 1 # 형식 2
for (( i = 0; i < 10; i++ )) for (( i = 0; i < 10; i++ ))
do {
echo $i echo $i
done }
---------------------------------------------------------------------------
$ arr=(11 22 33 44 55) i=0
$ for (( j = i + 1; j <= ${#arr[@]} - 2; ++j )) { echo ${arr[j]} ;}
22
33
44
$ for ((i=-50; i<50; i++)); do
echo "set yrange [-50:50]; plot $i * sin(x) lw 3"
sleep 0.1
done | gnuplot
-------------------------------
$ { echo "set isosample 100"
for ((i=-50; i<50; i++)); do
echo "spl [:] [:] [-50:50] $i * sin(x) * cos(y) with pm3d"
done
echo "pause mouse close"
} | gnuplot
------------------------------
# 소수는 seq 명령을 사용하거나 awk 를 활용
$ { echo "set isosample 100"
for i in `seq 0 0.001 0.1`; do
echo "spl [:] [:] [-1:1] sin($i * (x**2 + y**2)) with pm3d"
done
echo "pause mouse close"
} | gnuplot
$ awk 'BEGIN{
print "set isosample 100"
for(i=0; i<0.1; i+=0.001)
printf "spl [:] [:] [-1:1] sin(%g * (x**2 + y**2)) with pm3d;", i;
print "pause mouse close"
}' | gnuplot
break, continue 명령
break [N], continue [N]
while, until, for 반복문에서 사용되는 명령입니다. break 은 현재 실행 중인 반복문이 종료되는 결과를 갖고, continue 는 다음 처리할 원소로 넘어가는 역할을 합니다. 반복문은 중첩될 수가 있는데 이때 명령 인수로 숫자를 추가하면 상위 반복문에 적용시킬 수 있습니다.
반복문이 break 명령에 의해 종료될 경우 종료 상태 값은
0
이 됩니다.
# break # continue
for var1 in 111 222 333 for var1 in 111 222 333
do do
for var2 in aaa bbb ccc for var2 in aaa bbb ccc
do do
[ $var1 = 222 ] && break [ $var2 = bbb ] && continue
echo "$var1, $var2" echo "$var1, $var2"
done done
done done
111, aaa 111, aaa
111, bbb 111, ccc
111, ccc 222, aaa
333, aaa 222, ccc
333, bbb 333, aaa
333, ccc 333, ccc
------------------------------- --------------------------------
# break 2 # continue 2
for var1 in 111 222 333 for var1 in 111 222 333
do do
for var2 in aaa bbb ccc for var2 in aaa bbb ccc
do do
[ $var1 = 222 ] && break 2 [ $var2 = bbb ] && continue 2
echo "$var1, $var2" echo "$var1, $var2"
done done
done done
111, aaa 111, aaa
111, bbb 222, aaa
111, ccc 333, aaa
명령에 선행하는 대입 연산은 사용할 수 없다.
compound commands 에서는 명령에 선행하는 대입 연산은 사용할 수 없습니다.
$ str=hello if [ 1 = 1 ]; then echo $str; fi # if 명령에 선행해서 str 변수를 정의
bash: syntax error near unexpected token 'then'
$ str=hello while true; do echo $str; done
bash: syntax error near unexpected token 'do'
read 명령이 while, until 의 condition 에서 사용될 경우
read 명령이 while, until 문의 condition 에서 사용될 경우 loop 종료 후에는 해당 변수값이 empty 가 됩니다. ( read 에 실패해 종료됐으므로 )
$ while read line; do echo $line; done <<END
111
222
END
111
222
$ echo $line # while 문 종료 후에는 변수값이 empty 가 된다.
$
----------------------------------------
$ for var in 111 222; do echo $var; done # for 문의 경우
111
222
$ echo $var
222
Quiz
Go 언어를 설치하면 $GOROOT/src
디렉토리에 소스코드가 함께 설치되는데요.
프로그래밍을 하다 보면 사용 중인 함수가 어떻게 구현되어 있는지 보고 싶을 때가 있습니다.
사용자가 함수명을 인수로 전달하면 소스 디렉토리를 검색해서 함수 내용을 출력해 주는
스크립트를 작성하는 것입니다.
함수명이 여러 파일에 존재할 경우는 select
명령을 이용해서 선택할 수 있어야 합니다.
select
에서 사용되는case
문을 동적으로 생성하기 위해서는eval
명령을 사용합니다.
$ cat go-func
#!/bin/bash -e
if [ $# = 0 ]; then
echo >&2 "Usage: ${0##*/} function_name"
exit 1
fi
# Go 소스 파일명에는 공백문자나 glob 문자가 사용되지 않으므로
# 단어 분리나 globbing 방지 처리를 하지 않아도 됩니다.
found=( $(cd "$GOROOT" && find src -type f -name '*.go' \
-exec awk '/^func[^{]* '"$1"' *[[(]/ { print FILENAME; nextfile }' {} + ) )
if [ ${#found[@]} -gt 1 ]; then
PS3=$'\n>>> Select number: '
select pick in "${found[@]}"
do
eval "$(
echo 'case $pick in'
for v in "${found[@]}"; do echo "$v ) ;;"; done
echo 'esac'
)"
break 2
done
else
pick=${found[0]}
[ ${#found[@]} = 1 ] && echo "1) $pick"
fi
if [ -n "$pick" ]; then
sed -En '/^func[^{]* '"$1"'\(/{ p; /\{$/!b; :X n; p; /^}/!{bX}}' "$GOROOT/$pick" |
vi --not-a-term -R -c 'set ft=go' -
fi
---------------------------------------------------------------------
$ go-func Join
1) src/strings/strings.go 4) src/cmd/go/internal/web/api.go
2) src/bytes/bytes.go 5) src/path/filepath/path.go
3) src/path/path.go
>>> Select number: 1
다음은 type
의 내용을 찾아볼 수 있는 go-type
명령입니다.
$ cat go-type
#!/bin/bash -e
if [ $# = 0 ]; then
echo >&2 "Usage: ${0##*/} type_name"
exit 1
fi
found=( $(cd "$GOROOT" && find src -type f -name '*.go' \
-exec awk '/^type '"$1"'[[ ]/ { print FILENAME; nextfile }' {} + ) )
if [ ${#found[@]} -gt 1 ]; then
PS3=$'\n>>> Select number: '
select pick in "${found[@]}"
do
eval "$(
echo 'case $pick in'
for v in "${found[@]}"; do echo "$v ) ;;"; done
echo 'esac'
)"
break 2
done
else
pick=${found[0]}
[ ${#found[@]} = 1 ] && echo "1) $pick"
fi
if [ -n "$pick" ]; then
sed -En '/^type '"$1"'[[ ]/{ p; /\{$/!b; :X n; p; /^}/!{bX}}' "$GOROOT/$pick" |
vi --not-a-term -R -c 'set ft=go' -
fi
----------------------------------------------------------------
$ go-type Scanner
1) src/database/sql/sql.go 4) src/fmt/scan.go
2) src/text/scanner/scanner.go 5) src/bufio/scan.go
3) src/go/scanner/scanner.go
>>> Select number: 5
스크립트에서 사용된 sed 명령의 사용방법은 이곳 을 참고하세요.
스크립트에서 사용된 awk 명령의 사용방법은 이곳 을 참고하세요.
2 .
다음은 select 명령을 활용하여 사지선다형 퀴즈 문제를 작성하는 것입니다.
문제를 quiz.txt
파일에서 array 로 읽어들여서 반복문을 이용해 다음 문제로 넘어갈 수 있게 합니다.
#!/bin/bash -e
i=1
while read -r line; do
[[ "$line" =~ ^-+$ ]] && { let ++i; continue ;}
[ -n "$line" ] && ARR[i]+=$line$'\n'
done < quiz.txt || exit # quiz.txt 파일에서 문제를 array 로 읽어들입니다.
total_questions=${#ARR[@]} next_question=1 next=true
PS3=$'\n[ exit "x" ] 번호입력: '
while true; do
$next && eval "${ARR[next_question]}"
clear
echo "$Q" # 문제를 출력
echo
AA=( "${A1%?}" "${A2%?}" "${A3%?}" "${A4%?}" ) # 'O','X' 문자는 제외하고 입력
select pick in "${AA[@]}"
do
case $pick in
${AA[0]} ) OX=${A1: -1} ;;
${AA[1]} ) OX=${A2: -1} ;;
${AA[2]} ) OX=${A3: -1} ;;
${AA[3]} ) OX=${A4: -1} ;;
*)
[ "$REPLY" = "x" ] && break 2
echo -e "\n올바른 번호를 입력해 주세요.\n"
read -n1; next=false; clear; break
esac
echo
echo "$REPLY 번 \"${pick% }\""
if [ "$OX" = "O" ]; then
echo "@@@ 정답 @@@ 입니다."
next=true; let next_question++
[ "$next_question" -gt "$total_questions" ] && break 2
else
echo "틀렸습니다."
next=false
fi
echo
read -n1; break
done # select
done # while
echo
echo "설문이 종료되었습니다."
quiz.txt 파일 내용. 질문은 Q
변수에, 답은 A1 A2 A3 A4
변수에 설정하고 구분선은 ----
로 합니다.
$ cat quiz.txt
Q="앞뒤 생각하지 않고 덤빈다는 뜻의 '저돌적'이라는 말은 이 동물이 돌격한다는 뜻인데요.
아이큐가 약 70 정도로 지능이 높은 이 동물은 무엇일까요?"
A1="코뿔소 X" # 틀린답의 끝에는 'X' 문자를
A2="하마 X"
A3="돼지 O" # 정답의 끝에는 'O' 문자를 입력
A4="코끼리 X"
---------------------------------------------
Q="흔히 스포츠 경기에서 '간발의 차이'라는 표현을 쓰는데요.
여기서 '간발'은 얼마만큼의 간격을 뜻하는 말일까요?"
A1="발 하나조차 들어갈 수 없는 틈 X"
A2="발톱 하나조차 들어갈 수 없는 틈 X"
A3="손톱 하나조차 들어갈 수 없는 틈 X"
A4="머리카락 하나 조차 들어갈 수 없는 틈 O"