Bash 셸 스크립트 흐름 제어
기본 논리연산 정리
논리연산 | test , [ |
[[ ]] , (( )) |
---|---|---|
NOT | ! |
! |
AND | -a |
&& |
OR | -o |
|| |
condition evaluate
test
test
명령은 표현식이 성공하면(true이면) 종료 상태로 0
을 갖는다. 실패하면 1
을 갖는다.
따라서 간단하게 test
명령의 결과를 확인하려면 $?
를 출력해보면 된다.
$ test -e /dev/tty; echo $?
0
$ test -e ~/not-exist-file; echo $?
1
한편, test
명령과 [
는 같은 파일이다. [
는 if
와 함께 자주 사용하지만 if
의 문법이 아니다.
$ # 두 파일의 inode 가 같다
$ test /bin/test -ef /bin/[ ; echo $?
0
$ # inode를 출력해서 확인해보자
$ ls -il /bin/test /bin/[
1152921500312434706 -rwxr-xr-x 2 root wheel 134224 2023-06-15 Thu 19:08:29 '/bin/['
1152921500312434706 -rwxr-xr-x 2 root wheel 134224 2023-06-15 Thu 19:08:29 /bin/test
기본 연산
!
expression : expression 이 false 이면 true.- expression_1
-a
expression_2 : expression_1 과 expression_2 가 모두 true 이면 true. - expression_1
-o
expression_2 : expression_1 과 expression_2 중 하나라도 true 이면 true.-a
의 우선순위가-o
보다 높다.
(
expression)
: expression 이 true 이면 true.
파일 test
-e
file : 존재하는 파일이라면 true.
#!/usr/bin/env bash
if [ -e ~/.bashrc ]; then
echo "the file exists."
else
echo "the file does not exist."
fi
-d
directory : 존재하는 디렉토리라면 true.-x
file : 쓸 수 있는 파일이면 true.-f
file : 일반 파일이면 true.-L
file : 심볼릭 링크라면 true.-r
file : 읽을 수 있는 파일이면 true.-s
file : 크기가 0보다 큰 파일이면 true.-b
file : block special 파일이면 true.-c
file : character special 파일이면 true.-f
file : regular 파일이면 true.-p
file : named pipe 파일이면 true.-
-S
file : 네트워크 socket 파일이면 true. - file_1
-nt
file_2 : file_1 이 file_2 보다 최근에 수정된 파일이면 true. - file_1
-ot
file_2 : file_1 이 file_2 보다 오래된 파일이면 true. - file_1
-ef
file_2 : file_1 과 file_2 의 inode 번호가 같으면 true. - …
더 많은 옵션, 더 자세한 내용은 man test
를 참고할 것.
문자열 test
- 문자열이 비어있으면 false.
$ test "" ; echo $?
1
$ test "a" ; echo $?
0
-n
string : 문자열 길이가 0 이 아니면 true.-z
string : 문자열 길이가 0 이면 true.- string_1
=
string_2 : 두 문자열이 같으면 true.- sh 시절의 흔적.
- string_1
==
string_2 : 두 문자열이 같으면 true. - string_1
!=
string_2 : 두 문자열이 다르면 true. - string_1
\<
string_2 : string_1 이 string_2 보다 먼저 정렬되면 true. - string_1
\>
string_2 : string_1 이 string_2 보다 나중에 정렬되면 true.\<
,\>
비교에서\
를 사용하징 낳으면 리다이렉션에 사용되는<
,>
로 셸이 착각하니 주의할 것.
$ # > 를 잘못 사용하는 사례
$ # ↓ 그냥 사용하면 리다이렉션이 되어 버린다
$ if [ "aaaa" > "bbbb" ]; then echo "bad result"; else echo "good result"; fi
bad result
$ # if [ "aaaa" 의 출력이 bbbb 파일로 저장되어 있다
$ ls ./bbbb
./bbbb
$ # ↓ 백슬래시를 붙이면 제대로 동작한다
$ if [ "aaaa" \> "bbbb" ]; then echo "bad result"; else echo "good result"; fi
good result
$ ls ./bbbb
ls: cannot access './bbbb': No such file or directory
정수 test
- integer_1
-eq
integer_2 : 두 정수가 같으면 true. - integer_1
-ne
integer_2 : 두 정수가 다르면 true. - integer_1
-le
integer_2 : integer_1 ≤ integer_2 이면 true. - integer_1
-lt
integer_2 : integer_1 < integer_2 이면 true. - integer_1
-ge
integer_2 : integer_1 ≥ integer_2 이면 true. - integer_1
-gt
integer_2 : integer_1 > integer_2 이면 true.
[[ ]] 를 사용한 테스트 표현식
- string
=~
regexp : string 이 regexp 에 매치되면 true. - string
==
pattern : string 이 pattern 과 일치하면 true.
$ # * 을 확장해 파일 이름 패턴을 인식한다
$ if [[ "sample.txt" == sample.* ]]; then echo "ok"; fi
ok
$ # ""로 감싸면 확장을 하지 않는다
$ if [[ "sample.txt" == "sample.*" ]]; then echo "ok"; fi
(( )) 를 사용한 테스트 표현식
- 0 이 아니면 true.
$ (( 0 )) ; echo $?
1
$ (( 1 )) ; echo $?
0
$ (( 23 )) ; echo $?
0
if
if 를 사용하는 패턴은 보통 이런 식이다.
if [ -e "dir/file" ]; then
echo "file exists"
fi
if [ -d "dir" ]; then
echo "dir exists"
elif [ -f "file" ]; then
echo "file exists"
else
echo "not matched"
fi
a=5
b=10
if [ "$a" -eq 5 ] && [ "$b" -eq 10 ]; then
echo "true"
fi
a=5
b=20
if [ "$a" -eq 5 ] || [ "$b" -eq 10 ]; then
echo "true"
fi
다음과 같이 복잡하게 사용하는 것도 가능하지만, 이 정도로 복잡해지면 그냥 다른 프로그래밍 언어를 사용하는 것이 낫다.
a=5
b=20
c=30
if [ "$a" -eq 5 ] && ([ "$b" -eq 10 ] || [ "$c" -eq 30 ]); then
echo "true"
fi
case
#!/usr/bin/env bash
read -p "좋아하는 색깔은? " answer
case $answer in
red)
echo "빨간색을 좋아하시는군요"
;;
blue)
echo "파란색을 좋아하시는군요"
;;
white|black|gray)
echo "무채색을 좋아하시는군요"
;;
*)
echo "준비된 답변이 없습니다"
;;
esac
루프
- 아래의 모든 루프 형식에서
break
와continue
를 사용하는 것이 가능하다.
for .. in .. do .. done
for fileName in ~/Downloads/*; do
echo $fileName
done
아래와 같이 do
를 아랫줄로 내려도 된다.
for fileName in ~/Downloads/*
do
echo $fileName
done
확장 규칙을 사용해 이렇게 할 수도 있다.
for number in {1..30}; do
echo "이번 숫자는 $number 입니다"
done
건너뛰기가 필요하다면 뒤에 ..숫자
를 붙여주면 된다.
$ for number in {1..30..3}; do echo "이번 숫자는 $number 입니다"; done
이번 숫자는 1 입니다
이번 숫자는 4 입니다
이번 숫자는 7 입니다
이번 숫자는 10 입니다
이번 숫자는 13 입니다
이번 숫자는 16 입니다
이번 숫자는 19 입니다
이번 숫자는 22 입니다
이번 숫자는 25 입니다
이번 숫자는 28 입니다
아래와 같이 리스트 항목을 일일이 지정하는 것도 가능하다.
for color in red blue white; do
echo "이번 색깔은 $color 입니다"
done
배열을 사용하려 한다면 이렇게 하면 된다.
colors=("red" "blue" "white")
for color in "${colors[@]}"; do
echo "이번 색깔은 $color 입니다"
done
for ((i = 0; i < N; i++)); do .. done
C 계열 프로그래밍 언어에서 사용하는 for 문과 비슷하게 사용할 수도 있다.
for ((i=0; i<4; i++)); do
echo "인덱스: $i"
done
i
를 건너뛰며 증가시킬 필요가 있다면 +=
를 쓰면 된다.
for ((i=0; i<40; i+=10)); do echo "인덱스: $i"; done
인덱스: 0
인덱스: 10
인덱스: 20
인덱스: 30
while .. do .. done
i=0
while [ $i -lt 10 ]; do
echo "인덱스: $i"
i=$((i+1))
sleep 1
done
- 매 루프별로 1초씩 대기하며
인덱스: 0
부터인덱스: 9
까지 출력한다.
아래와 같이 한 줄로 작성할 수도 있다.
i=0; while [ $i -lt 10 ]; do echo "인덱스: $i"; i=$((i+1)); sleep 1; done
다음과 같이 ++
을 사용하는 것도 가능하다.
i=0
while \[[ $i -lt 10 ]]; do
echo "인덱스: $i"
((i++))
sleep 1
done
물론 --
도 사용할 수 있다.
i=10
while \[[ $i -gt 0 ]]; do
echo "인덱스: $i"
((i--))
sleep 1
done
while true; do .. done
무한 루프를 돌리고 싶다면 일반적인 프로그래밍 언어들과 비슷하게 while true
를 사용하면 된다.
while true; do
echo "무한 루프"
sleep 1
done
다음과 같이 true
대신 :
를 사용할 수도 있다.1
:
는 항상 성공으로 끝나는(항상 true) no operation 이다. ↩
while :; do
echo "무한 루프"
sleep 1
done
while read line; do .. done
read
명령을 사용하면 입력을 한 줄씩 읽어들일 수 있다.
cat /etc/passwd | while read line; do
echo $line
done
|
를 쓰지 않고 <
로 입력을 제공해도 된다.
while read line; do
echo $line
done < /etc/passwd
until .. do .. done
while
과 비슷하지만 조건이 반대이다. 나는 헷갈려서 안 쓰고 그냥 while
을 쓴다.
i=0
until [ $i -eq 10 ]; do
echo "인덱스: $i"
i=$((i+1))
sleep 1
done
주석
-
:
는 항상 성공으로 끝나는(항상 true) no operation 이다. ↩