Shell에서의 Exit Status에 관하여
Exit Status에 대하여 알아보자.
개요
지금은 소프트웨어나 프로그램이라고 하면 사람들은 거의 그래픽 UI를 가지는 프로그램을 생각 할 것이다. 하지만 Linux와 같은 UNIX계열의 운영체제에서는 쉘(SHELL)에서 많은 일을 수행한다
리눅스에서 어떤 프로그램을 설치 하기위해 구글링 하면 거의 대부분은 쉘에서 패키기 관리자(
apt
,dnf
,pacman
등)를 실행하여 프로그램을 설치 할 것이다.
복잡한 여러 명령을 반복적으로 수행해야 할때 쉘 스크립트를 작성하면 이러한 반복적인 작업을 매우 쉽고 빠르게 할 수 있다.
우리는 쉘에서 명령어를 실행 할때 해당 명령의 출력을 통해 프로그램이 정상적으로 실행되었는지를 판단 할 수 있다. 실행 도중 에러가 발생 하였다면 화면에 에러가 출력 될것이다.
전통적으로 UNIX 계열 프로그램은
ls
와 같이 명시적으로 정보를 출력을 요구 하는 명령이 아닌 경우 정상적으로 동작 하였을 경우 아무런 출력이 없는 경우가 많다. 이는 침묵은 금이라는 유닉스의 설계 철학에 따르는 것이다.
Speech is silver, but silence is gold.
그렇다면 쉘에서 실행한 명령이 정상적으로 수행되었는지 어떻게 판단 할까?
우리가 일반적으로 사용하는 방법은 명령이 실행되면서 출력되는 출력 값을 경험적으로 알고 있기 때문에 이 출력값을 기반으로 명령이 성공적으로 수행되었는지 확인한다. 하지만 이 방법은 사람에게는 매우 효율적인 방법이지만 쉘스크립트를 짜는 프로그래머 입장에서는 매우 번거로운 방법이다.
더 일반적으로 사용할 수 있는 방법을 찾아 보자.
모든 명령은 종료될 때 waitpid
시스템 콜(이나 이와 동등항 함수)에서 Exit Status를 반환하는데 (명시적으로 지정하지 않을 경우 기본값(0
)을 반환한다.) 프로그래머는 이 Exit Status를 가지고 자신의 실행한 명령이 정상적으로 실행 되었는지 판단 할 수 있다.
Exit Status
Exit Status는 O 에서 255 사이의 값을 가지고 쉘의 경우 125 이상의 값을 사용한다.
앞서 언급 했듯이 침묵은 금이기 때문에 많은 명령이 정상적으로 종료 되었다면 Exit Status로 0
을 반환한다.
사실 완전히 같은 의미는 아니지만 침묵은 금이라는 UNIX의 철학을 어느정도 따른다고 볼수 있다.
쉔에 요청한 명령이 존재 하지 않는 경우 Exit Status 127을 반환한다. 명령이 존재 하지만 실행 가능하지 않은 경우 126을 반환한다. 명령이 SIGINT
나 SIGSEGV
와 같은 Fatal Signal $N$에 의해 종료 된 경우 Exit Status는 $128 + N$ 이 된다.
그렇다며 이 0 ~ 125 까지의 Exit Status는 어떻게 정의 되는가? 많은 프로그램들이 POSIX에 정의된 errno.h를 따른다.
errno.h에는 자주 발생하는 에러를 정의해 놓았다. 프로그램이 비정상 적으로 종료 될때 errno.h를 참고 하여 반환값(Exit Status)을 결정 하도록 하자.
C나 C++ 개열의 함수도 마찬가지다. 함수의 수행 상태를 정수값으로 반환하는 함수의 경우 반환값을 errno.h를 참고 하여 반환 하도록 하는 것이 좋다.
메모리 비교 함수인 memcmp와 문자열 비교함수인 strcmp 계열의 함수를 보자. C에서는 0 이 아닌 값이 참(TRUE) 이지만 위 함수들은 비교 대상이 일치 할때
0
을 반환한다.
Shell Command(쉘 명령)
쉘 명령(Shell Command) 은 명령(Command) 과 그에 따르는 공백 문자로 구분된 인자(Argument) 들로 구성된다.
사실 쉘에서 사용되는 상수, 문자열과 몇몇 예약어를 제외한 모든 특수 문자와 문자의 조합은 쉘에 내장된 명령또는 일발적은 명령이다.
심지어 test
를 대신해서 쓰이는 [
까지 Shell에 내장된 명령어이다.
아래 명령을 실행 해보자
which [
BASH의 경우 다음과 같은 출력을 얻을 수 있다.
/usr/bin/[
ZSH의 경우 다음과 같은 결과를 얻을 수 있다.
[: shell built-in command
errno
유닉스 계열 시스템을 사용한다면 errno
명령으로 확인 할 수 있다.
errno -l
아래는 위 명령의 출력중 일부를 나타낸 것이다.
EPERM 1 Operation not permitted
ENOENT 2 No such file or directory
ESRCH 3 No such process
EINTR 4 Interrupted system call
EIO 5 Input/output error
ENXIO 6 No such device or address
E2BIG 7 Argument list too long
ENOEXEC 8 Exec format error
EBADF 9 Bad file descriptor
ECHILD 10 No child processes
EAGAIN 11 Resource temporarily unavailable
ENOMEM 12 Cannot allocate memory
.
.
.
errno
명령은moreutils
의 일부 이므로 이 명령이 없다면 해당 패키지를 설치 한다.
Debian 계열 (Debian, Ubunut, Mint…)
sudo apt install moreutils
Arch Linux 계열 (ArchLinux, Manjaro…)
sudo pacman -S moreutils
Redhat 계열 (Redhat, Fedora, Centos…)
sudo yum install moreutils
sudo dnf install moreutils
프로그래밍에서의 Exit Status, Return Code
아래와 같이 간단한 프로그램을 만든다고 가정해 보자. main()
함수에서 반환 하는 값이 Exit Status 가 되는데 앞서 설명 했듯이 0은 에러가 없는 상태를 나타내기 때문에 아래 예제에서는 0
을 반환 하였다.
#include <stdio.h>
int main(int argc, char **argv)
{
fprintf(stdout, "Hello!!");
return 0;
}
하지만 프로그램 실행중 문제 가 발생하여 프로그램을 종료 해야 하는 경우 0
을 반환 한하였다면 소스코드를 볼수 없는 프로그램 사용자들은 Exit Status가 0
이기 때문에 프로램이 정상적으로 종료되었다고 착각 할 수 있다.
따라서 프로그램이나 쉘에서 수행되는 스크립트를 을 작성하는 경우 등으로 함수를 작성할때 반환되는 값을 POSIX에 정의된 errno를 참고하도록 하자.
리눅스에서 많이 사용되는 주요 프로그램들 대부분이
errno
의 규칙을 따르지만 그렇지 않은 프로그램도 있다. 해당 프로그램의 매뉴얼을 읽어보자.(RTFM)
쉘에서의 Exit Status
쉘에서 실행한 명령의 종료 상태는 $?
변수에 저장된다.
아래 명령을 수행 해보자.
ls
echo $?
화면에 0
이 출력된다. ls 명령이 정상적으로 종료 된 것이다.
그렇다면 아래와 같이 ls
명령 다음에 존재 하지 않는 파일이나 디렉터리의 이름을 넣어 보자.
ls asdfasdfasdf
echo $?
화면에 2
가 출력된다.
POSIX errno.h
에는 errno 2
는 ENOENT
로 정의 되어 있으며 파일이나 디렉토리가 없을 경우 반환 하는 값니다.(No such file or directory
)
Lists
리스트라는 개념이 있다. 우리가 알고 있는 리스트와는 조금 다르다. 아래는 BASH 메뉴얼에 있는 리스트에 대한 정의 이다.
A List is a sequence of one or more pipelines separated by one of the operators
;
,&
,&&
, or||
‚ and optionally terminated by one of;
,&
, or<newline>
.
리스트는 ;
, &
, &&
, 또는 ||
연산자중 하나로 구분되고 선택적으로 ;
, &
, 또는 <newline>
으로 종료되는 하나 이상의 파이프라인 시퀀스다.
&&
연산자
Logical AND 연산과 같다.
command-one && command-two
command-one
이 참일 경우에만 command-two
가 실행된다.
||
연산자
Logical OR 와 같다.
command-one || command-two
command-one
이 거짓 일 경우에만 command-two
가 실행된다.
이 &&
와 ||
연산자를 활용하면 간단히 if
-else
문과 같은 효과를 볼 수 있다.
디렉터리가 존재 할 경우 디렉터리를 삭제
[ -d /home/test/aaa ] && rm -rf /home/test/aaa
디렉터리가 존재하지 않을 경우 디렉터리를 생성
[ -d /home/test/aaa ] || mkdir -p /home/test/aaa
파일이 존재 하고 실행 권한이 있는 경우 파일을 실행
[ -x /bin/errno ] && errno -l
파일이 존재 하지 않을 경우 파일 생성
[ -f /home/test/aaa ] || touch /home/test/aaa
&&
연산자 사용시 주의점
[ -d /home/test/aaa ] && rm -rf /home/test/aaa
위 명령은 [ -d /home/test/aaa ]
이 참일 경우 rm -rf /home/test/aaa
명령을 실행하라는 뜻이다.
/home/test/aaa
디렉터리가 존재 하지 않을 경우 [ -d /home/test/aaa ]
명령이 거짓이 되어 rm -rf /home/test/aaa
명령이 실행 되지 않게 된다. 그리고 이 List의 Exit Status는 [ -d /home/test/aaa ]
의 결과인 1
이 된다.
Makefile 등에서 이 명령을 사용하였을 때 Exit 가 참이 아니기 때문에 다음 명령이 수행되지 않는다.
/home/test/aaa
가 디렉토리 일 경우 이 디렉토리를 삭제하고 빈 파일 /home/test/aaa
를 생성하고자 아래와 같이 Makefile
을 작성 했다면
all:
[ -d /home/test/aaa ] && rm -rf /home/test/aaa
touch /home/test/aaa
[ -d /home/test/aaa ] && rm -rf /home/test/aaa
의 결과가 거짓이기 때문에 다음 명령인
touch /home/test/aaa
수행 되지 않는다.
명령 다음에 ||
연산자와 true
명령을 추가 하자.
true
명령은 Exit Code0
를 반환한다.
Makefile
all:
[ -d /home/test/aaa ] && rm -rf /home/test/aaa || true
touch /home/test/aaa
References
https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html