이런저런 쉘스크립트를 보다면 스크립트의 문장 끝부분이 다음과 같은 구문을 종종 보게된다.
cat 명령은 error.txt 파일의 내용을 출력하는 명령이고…
> 는 리다이렉션으로 화면에 출력되는 내용을 > 다음에 지정한 파일로 보내는 것이니 /dev/null 로 결과를 보내고, 즉 화면에는 표시하지 않고…까지는 유닉스 환경을 다루어본 학생이나 엔지니어라면 쉽게 이해한다.
문제는 2>&1 이다.
흔히 “아~저건 에러메시지도 화면에 표시하지 않게하는 거지.”라며 아는 척~~하는 사람들도 많다. 맞다. 정확하게 알고 있긴하다.
쉘스크립트를 작성하고 실행할 때 중간에 에러가 발생하게 되면 에러메시지가 화면에 고스란히 출력되어 보기에 썩~좋지 않기도 하고 에러가 많거나 계속 다른 메시지가 출력되면서 화면이 스크롤되어 에러를 확인할 수 없게 되는 경우가 있다. 그럴 경우 로그파일에 에러메시지를 기록하도록 하기 위해 2>&1 을 사용해 에러메시지를 로그파일에 기록하고 화면은 깔끔하게 유지하도록 한다.
하지만 2>&1이 의미하는 정확한 뜻을 이해하는 것이 엔지니어의 본분이 아닐까..??
파일디스크립터와 표준입력/표준출력/표준에러
C프로그래밍을 해본 사람들은 잘 알고 있어야 하는 것이 파일디스크립터다.(윈도에서는 핸들이라고 부른다) 프로그램이 수행되면 운영체제는 실행되는 프로그램에게 3개의 기본 파일디스크립터를 할당해준다. 그리고 그 프로그램이 내부적으로 다른 파일을 open하게 되면 운영체제는 4번째 파일디스크립터를 할당한다.
운영체제가 프로그램에게 할당하는 세개의 파일디스크립터는 다음과 같다.
파일디스크립터 |
설 명 |
0 |
표준 입력 (standard input) |
1 |
표준 출력 (standard output) |
2 |
표준 에러 (statndard error) |
이해가 되는가? 만약 프로그램을 작성하고 프로그램 내부에서 파일을 열게 되면 파일디스크립터는 3부터 할당된다.
프로그램이 실행되면 운영체제는 프로그램에게 어디로부터 입력을 받고 연산결과를 어디로 출력하고 에러가 발생하면 에러를 어디로 출력할지 정해주어야 한다. 그 기본값이 바로 표준 입력, 표준출력, 표준 에러다. (표준입출력장치 및 표준에러장치라고 부른다.)
표준입력
표준입력장치는 기본적으로 키보드다. 프로그램이나 쉘에서 표준 입력장치를 변경하지 않으면 프로그램은 키보드 입력을 표준입력으로하여 기다린다.
예를 들어 다음과 같이 cat 명령을 실행하면..
$ cat /etc/hosts
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1 localhost.localdomain localhost
192.168.100.10 ncsd loghost
$
cat 명령은 프로그램 내부적으로 첫번째 인자(아규먼트)로 지정된 파일을 사용하도록 프로그래밍되어 있다. 따라서 cat 명령 다음에 지정된 /etc/hosts 파일을 열고 내용을 출력한다. 하지만 cat 명령 뒤의 인자인 /etc/hosts를 지정하지 않고 cat 명령만 입력한 뒤 실행하게 되면 cat 명령은 표준입력장치인 키보드로 부터 입력을 받도록 프로그래밍 되어 있다.
즉 다음과 같이 동작한다.
$ cat <– 파일명 없이 실행
abcdef <– 키보드에서 입력하고 엔터키를 입력
abcdef <– 키보드에서 입력받은 내용을 화면에 출력한 부분
keyboard input <— 역시 키보드로 입력한 내용
keyboard input <— 키보드에서 입력받은 내용을 화면에 출력
<– 다른 키 입력없이 엔터만 입력
<– 엔터만 출력
$ <– ctrl + c 키를 눌러 종료
그리고 cat은 표준출력장치로 자신에게 기본적으로 할당된 표준출력장치를 사용하도록 되어 있다. 변경하지 않을 경우 모든 프로그램들은 모니터 디바이스를 표준출력장치로 사용하도록 되어 있다. 그래서 위의 예제에서 모두 결과가 화면에 출력되는 것이다.
표준 입력장치 변경하기
쉘에서 표준 입력장치를 변경하는 방법은 두가지가 있다. 바로 리다이렉션과 파이프다. 먼저 리다이렉션을 사용하여 표준 입력장치를 변경하는 예제다.
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1 localhost.localdomain localhost
192.168.100.10 ncsd loghost
$
$ cat < /etc/hosts
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1 localhost.localdomain localhost
192.168.100.10 ncsd loghost
$
리다이렉션을 사용하여 표준 입력장치를 변경할 때는 일반적으로 명령어 뒤에 < 기호를 입력하고 장치명(파일명)을 사용한다.
위의 두 예제의 실행 결과는 동일하다. 하지만 프로그램 내부의 동작은 전혀 다르다는 것을 이해해야 한다.
cat/etc/hosts 명령은 cat 명령에게 /etc/hsots 파일을 열도록 지정한 것이다.
하지만 cat < /etc/hosts 명령은 아규먼트 없이 실행될 경우 사용하도록 지정된 표준입력장치인 키보드(standard input)를 /etc/hsots 라는 파일로 바꾸도록 지정하여 파일의 내용을 표준입력으로 리다이렉트 받아 실행한다.
결과는 같지만 내부적인 수행과정은 전혀 다른 것이다.
표준출력과 표준출력장치 변경
표준 출력도 마찬가지다. 표준입력은 < 기호를 이용하여 변경했다. 표준 출력은 > 기호를 이용해 변경한다.
따라서 맨 앞에서 예를 든 cat /tmp/error.txt > /dev/null 이 바로 cat 명령의 표준 출력을 화면이 아닌 /dev/null로 바꾼 것이다. 즉 cat /tmp/error.txt 명령에 의해 화면에 출력될 error.txt의 내용을 화면으로 출력하지 않고 /dev/null 이라는 파일로 출력하는 것이다. (하지만 /dev/null은 운영체제에서 사용하는 블랙홀과 같은 장치파일이다. )
하지만 문제는 cat/tmp/error.txt > /dev/null 은 error.txt 파일이 있다면 아무런 메시지를 화면에 출력하지 않고 정상 동작하지만 error.txt 파일이 없다면 아래화면 처럼 에러메시지를 화면에 출력한다.
$ cat /tmp/error.txt > /dev/null
cat: /tmp/error.txt: No such file or directory
$
위의 예제는 표준 출력만을 바꾼 것이다. 그런데 이 문장은 사실 무언가가 생략된 문장이다. 표준 파일 디스크립터에는 출력에 대한 디스크립터가 2개다. 표준 출력과 표준 에러가 그것이다. 따라서 위의 예제는 다음과 같이 쓰슨 것이 정상이다.
$ cat /tmp/error.txt 1> /dev/null
cat: /tmp/error.txt: No such file or directory
$
리다이렉션 기호인 > 앞에 1, 즉 표준출력을 의미하는 파일 디스크립터를 써주는 것이 정확한 표현이다. 다만 파일 디스크립터를 생략하면 두개의 출력 디스크립터 중 표준 출력이라고 묵시적으로 약속이 되어 있는 것이다.
자 그렇다면 이 포스트의 맨앞에서 나왔던 다음 문장을 이해해보자.
/dev/null 까지는 이해했을 테고….
뒤의 2> 도 이해가 되어야 한다. 즉 2번 파일디스크립터인 표준에러다. 즉 표준에러 출력을 > 다음의 장치(파일)로 변경한다는 의미다. 그런데 &1 이 사용되었다. 1은 표준 입력/출력/에러 장치에서 표준 출력을 의미한다. 즉 표준출력의 출력장치로 지정된 장치(파일)을 표준에러 출력장치로 함께 사용한다는 의미다. 즉 에러가 발생하면 에러 메시지를 /dev/null로 리다이렉트한다는 의미다. 앞에서 표준 출력장치(1)이 /dev/null로 변경(리다이렉트)되었기 때문이다.
예제에서는 /dev/null 을 표준출력장치로 리다이렉트 했지만 실제 쉘스크립트를 작성할 때는 스크립트의 수행 중 발생되는 출력과 에러를 기록하는 로그파일로 지정하는 것이 바람직하다고 하겠다. 그래야 스크립트의 실행결과와 에러를 정확하게 파악하고 디버깅을 할 수 있기 때문이다.
이해가 안된다면 이해가 될때까지 반복해서 읽어보고 테스트를 해봐야 한다.
이전포스트 보기 : 쉘스크립트에서 사칙연산과 문자열 자르기