Exit Status
Shell 은 명령을 다룹니다. 그리고 실행되는 모든 명령은 종료 상태 값을 반환합니다.
if
, while
, until
, &&
, ||
모두 종료 상태 값을 사용해서 참, 거짓을 판단합니다.
프로그래밍 언어에서 처럼 특정값 (숫자나 스트링 또는 예약어) 를 사용하지 않습니다.
# 종료코드 0 이 참이라고 해서 다음과 같이 사용할 수 없습니다.
$ if 0; then echo 111; else echo 222; fi
0: command not found
$ 0 && echo 111
0: command not found
명령의 종료 상태 값은 $?
변수를 통해 알아볼 수 있습니다.
$ true; echo $? # 정상 종료 상태 값은 '0'
0
$ false; echo $? # '0' 이외의 값은 비정상 종료 상태 값
1
..........................................................
curl -O ftp.kaist.ac.kr/ubuntu-cd/bionic/ubuntu-18.04-desktop-amd64.iso
if test $? = 0; then
echo "download succeeded"
else
echo "download failed"
fi
앞선 명령이 정상 종료되어야 할 경우
앞서 실행된 명령이 비정상 종료하였을 경우
뒤에 이어지는 명령이 실행되면 안될 경우가 있습니다.
이럴 때는 다음과 같이 &&
연산자를 이용해 명령을 연결하거나
test 명령을 이용해 직접 $?
값을 체크합니다.
$ command1 ... && command2 ... && command3 ...
----------------------------------------------
command ....
if test $? != 0; then
echo command failed
exit 1
fi
앞선 명령의 종료 상태 값과 상관없이 이후 명령이 실행되려면
$ command1 ...; command2 ...; command3 ...
종료 상태 값 지정
script 파일이나 subshell 은 프로세스가 새로 생성되는 것이므로 종료 상태 값을 지정할때 exit 명령으로 합니다. function 이나 source 명령으로 읽어들이는 경우에는 return 명령으로 합니다. 종료 상태 값을 지정하지 않으면 마지막으로 실행된 명령의 종료 상태 값이 사용됩니다.
# 마지막 명령의 종료 상태 값이 사용되므로 따로 return 명령을 사용할 필요가 없다.
command_exist () {
which "$1" > /dev/null;
# return $?
}
if command_exist vim; then
...
fi
--------------------------------------------------------------------------------
# 다음의 경우는 함수 중간에서 return 명령을 사용하였지만 종료 상태 값을 지정하지 않았는데요.
# 이때도 자동으로 마지막 명령의 종료 상태 값이 사용됩니다.
cd() {
...
builtin cd "$@"
return
...
...
}
종료코드에는 1 byte 를 사용하므로 0 ~ 255 번을 사용할 수 있습니다.
그중에 0
만 정상 종료를 나타내고 나머지는 오류를 분류해 나타내는데 사용됩니다.
1, 2, 126 ~ 165 번은 shell 에서 사용됩니다.
0
정상종료 ( Success )
1
일반적인 에러
let "var = 1 / 0" : division by 0
2
Syntax error, 잘못 사용된 builtin 명령
test.sh: line 6: syntax error near unexpected token 'fi'
exit: 3.14: numeric argument required
126
명령을 실행할 수 없음
명령은 존재하지만 excutable 이 아니거나 퍼미션 문제
bash: ./mylogfile.txt: Permission denied
127
명령 (파일) 이 존재하지 않음
typo 또는
$PATH
문제
asdfg: command not found
128 + N
Signal N 에의한 종료.
가령 kill -9 PID 로 종료 됐다면
$?
값은 128 + 9 = 137
참, 거짓 판단에 복수개의 명령이 사용될 경우
참, 거짓의 판단에 사용되는 명령이 위치하는 자리에는 꼭 하나의 명령만 올수 있는 것은 아닙니다.
{ ;}
, ( )
를 이용한 명령 그룹이 올 수도 있고 ;
를 이용해서 여러개의 명령을 사용할 수도 있습니다.
또한 |
파이프를 이용해서 여러 명령이 연결될 수도 있는데 어떤 경우이건 모두 마지막으로 실행되는 명령의
종료 상태 값이 참, 거짓의 판단에 사용됩니다.
# 마지막 명령인 test -z 의 종료상태 값이 참, 거짓 판단에 사용된다.
until read -r line
line=$(echo "$line" | tr -d '\r\n')
test -z "$line"
do
...
done
pipe 로 연결된 명령의 종료 상태 값
pipe 로 여러 명령이 연결되어 실행될 때는 마지막 명령의 종료 상태 값이 사용됩니다.
따라서 다음 첫번째 명령문은 항상 종료 상태 값으로 0
을 반환합니다.
왜냐하면 command1 의 실패에 상관없이 sed 명령은 항상 참을 반환하기 때문입니다.
command1 명령이 실패하였을 때 비정상 종료 상태 값이 반환되려면
두 번째와 같이 pipefail 옵션을 사용해야 합니다.
pipefail 옵션은 파이프에 연결된 명령들 중에 하나라도 실패가 생길 경우
비정상 종료 상태 값을 반환합니다.
command1 arg1 arg2 | sed -n '/<main>:/,/^$/p'
..................................................
# 옵션 설정이 현재 shell 에 적용되지 않도록 subshell ( ) 을 사용.
(
set -o pipefail
command1 arg1 arg2 | sed -n '/<main>:/,/^$/p'
)
sh
의 경우는 pipefail 옵션을 사용할 수 없으므로 다음과 같이 명령을 분리해 작성하거나
redirections 에제 와 같은 방법을 이용할 수 있습니다.
# 명령을 두개로 분리해서 작성
command1 arg1 arg2 > $tmpfile
status=$?
sed -n '/<main>:/,/^$/p' $tmpfile
echo $status
null 값인 변수는 참일까 거짓일까?
존재하지 않는 변수나 null 값인 변수는 quote 하지 않을경우 참이 되므로 주의해야 합니다.
$ asdf=""
$ if $asdf; then echo 111; fi
111
$ unset asdf
$ $asdf && echo 111
111
대입 연산의 종료 상태 값은?
=
, +=
메타문자를 이용한 식은 명령문이 아닙니다. 그래서 ;
로 구분없이 한줄에 여러개를 쓸 수도 있습니다. 종료 상태 값은 기본적으로 항상 0
이지만 명령치환과 함께 사용될 경우는 명령치환 종료 상태 값을 따릅니다.
# 대입연산은 명령문이 아니므로 한줄에 여러개를 쓸수도 있다.
$ AA=11 BB=22 CC=33
$ echo $AA $BB $CC
11 22 33
---------------------------------------
$ [ 1 -eq 2 ]
$ echo $?
1
# 대입연산의 종료 상태 값은 기본적으로 0 이다
$ [ 1 -eq 2 ]
$ AA=11
$ echo $?
0
-------------------------------------------------
# 명령치환과 사용될 경우 명령치환 종료 상태 값을 따른다.
$ readlink -e asdfg; echo $?
1
$ AA=$( readlink -e asdfg ); echo $?
1
$ readlink -e test.sh; echo $?
/home/mug896/tmp/test.sh
0
$ AA=$(readlink -e test.sh); echo $?
0
$ echo $AA
/home/mug896/tmp/test.sh
# 그러므로 if 문에서 사용할 수도 있다.
if AA=$( readlink -e test.sh ); then ...
if ! options=$(getopt -o a:f:c -- "$@"); then
usage
exit 1
fi
# 단 local, declare 와 함께 사용될 경우는 적용되지 않습니다.
$ AA=$( readlink -e asdfg ); echo $?
1
$ declare AA=$( readlink -e asdfg ); echo $?
0
Quiz
ftp 명령을 사용해 보면 작업 성공에 상관없이 항상 종료 상태 값으로 0
을 반환하는 것을 볼 수 있습니다.
이것은 ftp 작업 실패시 다음 실행할 명령을 컨트롤하는데 문제가 되는데요.
어떻게 하면 작업 실패를 구분할 수 있을까요?
$ ftp -in ftp.kaist.ac.kr <<\END
user anonymous 1234
cd /ubuntu-cd/bionic
get MD5SUMS_XXX # MD5SUMS_XXX 은 존재하지 않는 파일
bye
END
Failed to open file.
$ echo $? # 작업에 실패하였지만 종료 상태 값으로 '0' 을 반환된다.
0
ftp 명령의 -v
(verbose) 옵션을 사용하면 ftp 서버에 접속해서 실행하는 각각의
명령에 대해서 서버 응답을 받을 수 있습니다.
이 응답 메시지를 grep 명령을 이용해 작업 성공 여부를 판단할 수 있습니다.
# login 에 성공했을 때
230 Login successful.
# cd 에 성공했을 때
250 Directory successfully changed.
# get file 에 성공했을 때
226 Transfer complete.
-----------------------------------
# grep 명령을 이용해 파일전송 성공 여부를 체크
# '|' 파이프로 명령이 연결될 경우 마지막 명령의 종료 상태 값이 사용된다.
$ ftp -v -in <<\END | grep -q '226 Transfer complete'
open ftp.kaist.ac.kr
user anonymous 1234
cd /ubuntu-cd/bionic
get MD5SUMS_XXX
bye
END
$ echo $? # 실패
1
.....................................................
$ ftp -v -in <<\END | grep -q '226 Transfer complete'
open ftp.kaist.ac.kr
user anonymous 1234
cd /ubuntu-cd/bionic
get MD5SUMS
bye
END
$ echo $? # 성공
0