Unix Like 시스템의 Shell에서 Exit Status 에 관한 고찰

Share
Unix Like 시스템의 Shell에서  Exit Status 에 관한 고찰
Photo by Keagan Henman / Unsplash

개요

소프트웨어프로그램을 개발 한다고 하면 대부분의 사람들은 노트 패드나 웹 브라우저를 통해 보여지는 사이트 같이 UI를 가지는 프로그램을 생각 할 것이다.

하지만 아직까지 UI 없이 쉘(SHELL)에서 실행 되는 프로그램들이 많이 사용되고 있을 뿐 아니라 개발 되고 있다.

예를 들어 리눅스 시스템에 nginx 웹서버를 설치를 위해 구글에 nginx 설치 법을 검색하면 UI 기반 패키기 관리자가 아닌 CLI 기반 패키지 관리자(apt, dnf, pacman 등)를 실행하여 프로그램을 설치 하는 방법을 알려 줄 것이다.

사람들은 터미널 환경에서 키보드를 두드리는 것 보다 UI 환경이 마우스를 클릭 하는 일이 편하기 느껴질 수 있지만 익숙해지면 마우스를 클릭 하는것 보다 키보드를 두드리는 것이 더 효율적임을 알 수 있다.

게임이나 상용 프로그램에서 마우스를 움직이고 클릭 하는것 보다 정해진 단축 키를 두드리는 것이 더 빠르다는 것을 알것이다. 중요한것은 익숙함 이다.

이것에 더해 복잡한 여러 동작을 반복적으로 수행해야 한다면 명령들로 구성된 쉘 스크립트를 작성하면 작업을 매우 쉽고 빠르게 처리 할 수 있다.

주어진 과제를 처리 하기위해 하나의 명령이 아니라 여러 명령을 순차적으로 실행해야 결과값을 얻수 있는 경우를 생각해보자. 앞서 실행한 명령이 실패하더라도 다음 명령을 실행해야 하는 경우 보다는 그 반대의 경우가 더 많을 것이다.

예를들어 파일을 압축하고 압축된 파일을 백업 서버에 업로드 하는 일 과제

이를 위해서는 쉘에서 실행된 명령이 정상적으로 실행 되었는지 판단 할 수 있는 기준이 필요 하다.

통상적으로 쉘에서 파일 복사 (cp)이나 파일이동(mv)명령어를 실행 할때 명령에 대한 출력을 통해 명령이 정상적으로 실행 되었는지 판단할 수 있다. 실행 도중 에러가 발생 하였다면 화면에 에러가 출력 될것이다.

전통적으로 UNIX 계열 프로그램은 ls와 같이 명시적으로 정보의 출력을 요구 하는 명령이 아닌 경우 정상적으로 실행 되었을 경우 아무런 출력이 없는 경우가 많다. 이는 침묵은 금이라는 유닉스의 설계 철학에 따르는 것이다.

Speech is silver, but silence is gold.

그렇다면 쉘에서 실행한 명령이 정상적으로 수행되었는지 어떻게 판단 할까?

일반적으로 사람이 터미널에서 실행한 명령이 정상적으로 종료 되었는지 확인하는 방법은 경험론 적인다. 사용하는 방법은 명령이 실행되면서 출력되는 출력 값을 경험에 의해 학습된 출력 값과 터미널에서 출력된 값을 비교 하는 것이다. 이 방법은 사람에게는 매우 효율적인 방법이지만 쉘스크립트를 짜는 프로그래머나 이를 실행하는 컴퓨터 입장에서는 매우 번거로운 방법이다.

쉘 스크립트나 프로그램에서 사용할 수 있는 방법을 찾아 보자.

모든 명령은 종료될 때 waitpid 시스템 콜(이나 이와 동등항 함수)에서 자식 프로세서의 종료 상태(Exit Status)를 반환하는데 (명시적으로 지정하지 않을 경우 기본값(0)을 반환한다.) 프로그래머는 이 Exit Status를 가지고 자신의 실행한 명령이 정상적으로 실행 되었는지 판단 할 수 있다.

Exit Status

Exit Status는 O 에서 255 사이의 값을 가지고 쉘의 경우 125 이상의 값을 사용한다.

앞서 언급 했듯이 침묵은 금이기 때문에 많은 명령이 정상적으로 종료 되었다면 Exit Status0을 반환한다.

사실 완전히 같은 의미는 아니지만 침묵은 금이라는 UNIX의 철학을 어느정도 따른다고 볼수 있다.

쉔에 요청한 명령이 존재 하지 않는 경우 Exit Status 127을 반환한다. 명령이 존재 하지만 실행 가능하지 않은 경우 126을 반환한다. 명령이 SIGINTSIGSEGV와 같은 Fatal Signal $N$에 의해 종료 된 경우 Exit Status는 $128 + N$ 이 된다.

그렇다며 이 0 ~ 125 까지의 Exit Status는 어떻게 정의 되는가? 많은 프로그램들이 POSIX에 정의된 errno.h를 따른다.

errno.h에는 자주 발생하는 에러를 정의해 놓았다. 프로그램이 비정상 적으로 종료 될때 errno.h를 참고 하여 반환값(Exit Status)을 결정 하도록 하자.

CC++ 개열의 함수도 마찬가지다. 함수의 수행 상태를 정수값으로 반환하는 함수의 경우 반환값을 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 Status0이기 때문에 프로램이 정상적으로 종료되었다고 착각 할 수 있다.

따라서 프로그램이나 쉘에서 수행되는 스크립트를 을 작성하는 경우 등으로 함수를 작성할때 반환되는 값을 POSIX에 정의된 errno를 참고하도록 하자.

리눅스에서 많이 사용되는 주요 프로그램들 대부분이 errno의 규칙을 따르지만 그렇지 않은 프로그램도 있다. 해당 프로그램의 매뉴얼을 읽어보자.(RTFM)

쉘에서의 Exit Status

쉘에서 실행한 명령의 종료 상태는 $? 변수에 저장된다.

아래 명령을 수행 해보자.

ls 
echo $?

화면에 0이 출력된다. ls 명령이 정상적으로 종료 된 것이다.

그렇다면 아래와 같이 ls 명령 다음에 존재 하지 않는 파일이나 디렉터리의 이름을 넣어 보자.

ls asdfasdfasdf
echo $?

화면에 2가 출력된다.

POSIX errno.h에는 errno 2ENOENT로 정의 되어 있으며 파일이나 디렉토리가 없을 경우 반환 하는 값니다.(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에서 수행한 명령이 0를 반환하지 않으면 순차적인 명령 실행을 중단하고 프로그램을 종료 한다. 따라서 우리의 의도대로 동작 하지 않는다. 아래 예제를 보자.

/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 Code 0를 반환한다.

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

Read more

맥스웰 방정식과 전자기파

맥스웰 방정식과 전자기파

맥스웰 방정식의 4개의 연립 미분 방정식을 통해 전자기파를 예언 하였던 유도 과정을 알아 보자. 멕스웰 방정식 * 전기장의 발산(쿨롱의 법칙) $$ \boldsymbol{\nabla} \boldsymbol{\cdot} \mathbf{E} = \frac{\rho}{\varepsilon_0} \tag{1} $$ * 자기장의 발산(자하부존재) $$ \boldsymbol{\nabla} \boldsymbol{\cdot} \mathbf{B} = 0 \tag{2} $$ * 전기장의 회전(페러데이의전자기유도) $$ \boldsymbol{\nabla} \boldsymbol{

By euikook
3전압계법 - 단상 전력을 측정하는 법

3전압계법 - 단상 전력을 측정하는 법

개요 직류(DC) 회로에서 전력을 구하려면 부하전압(부하에 인가되는 전압)과 부하전류(부하에 흐르는 전류)의 곱으로 계산할 수 있지만 교류(AC) 회로에서 부하전압과 부하전류간 위상차가 존재하기 때문에 전원측에서 공급하는 전력과 실제 부하에서 소비되는 전력과 크기가 다르다. 교류 회로에서는 전원측에서 공급되는 전력을 피상전력($P_a$)이라고 하고 실제 부하에서 소비되는

By euikook
2전력계법 - 3상 전력을 측정하는 방법

2전력계법 - 3상 전력을 측정하는 방법

2 전력계법 이전 글에서 단상부하의 경우 유효전력을 측정하기 위한 3 전압계법과 3 전류계법에 대하여 알아보았다. 이번에는 평형 삼상회로 부하에서 2개의 유효전력계를 이용하여 3상 유효 전력을 측정하는 방법에 대해 알아본다. 유효전력계는 전압계, 전류계, 역률계로 구성되어 있으므로 전압계, 전류계, 역률계가 각각 2개씩 있으면 유효전력을 측정할 수 있다. 세상의 전압과 전류의 크기가 같고

By euikook
3전류계법 - 단상 전력을 측정 하는 방법

3전류계법 - 단상 전력을 측정 하는 방법

개요 이전 글인 3전압계법을 다룬 글에서 이미 설명 했듯이 교류(AC) 회로에서 부하전압과 부하전류간 위상차가 존재하기 때문에 전원측에서 공급하는 전력과 실제 부하에서 소비되는 전력과 크기가 다르다. 교류 회로에서는 전원측에서 공급되는 전력을 피상전력($P_a$)이라고 하고 실제 부하에서 소비되는 전력을 유효전력($P$)이라고 한다. 피상전력으로부터 유효전력을 계산하기위해 역률($\cos{\theta}

By euikook