이런저런 쉘스크립트를 보다면 스크립트의 문장 끝부분이 다음과 같은 구문을 종종 보게된다.
cat /tmp/error.txt > /dev/null 2>&1
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은 표준출력장치로 자신에게 기본적으로 할당된 표준출력장치를 사용하도록 되어 있다. 변경하지 않을 경우 모든 프로그램들은 모니터 디바이스를 표준출력장치로 사용하도록 되어 있다. 그래서 위의 예제에서 모두 결과가 화면에 출력되는 것이다.
# 표준 입력장치 변경하기
쉘에서 표준 입력장치를 변경하는 방법은 두가지가 있다. 바로 리다이렉션과 파이프다. 먼저 리다이렉션을 사용하여 표준 입력장치를 변경하는 예제다.
$ 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
# 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, 즉 표준출력을 의미하는 파일 디스크립터를 써주는 것이 정확한 표현이다. 다만 파일 디스크립터를 생략하면 두개의 출력 디스크립터 중 표준 출력이라고 묵시적으로 약속이 되어 있는 것이다.
자 그렇다면 이 포스트의 맨앞에서 나왔던 다음 문장을 이해해보자.
cat /tmp/error.txt > /dev/null 2>&1
/dev/null 까지는 이해했을 테고....
뒤의 2> 도 이해가 되어야 한다. 즉 2번 파일디스크립터인 표준에러다. 즉 표준에러 출력을 > 다음의 장치(파일)로 변경한다는 의미다. 그런데 &1 이 사용되었다. 1은 표준 입력/출력/에러 장치에서 표준 출력을 의미한다. 즉 표준출력의 출력장치로 지정된 장치(파일)을 표준에러 출력장치로 함께 사용한다는 의미다. 즉 에러가 발생하면 에러 메시지를 /dev/null로 리다이렉트한다는 의미다. 앞에서 표준 출력장치(1)이 /dev/null로 변경(리다이렉트)되었기 때문이다.
예제에서는 /dev/null 을 표준출력장치로 리다이렉트 했지만 실제 쉘스크립트를 작성할 때는 스크립트의 수행 중 발생되는 출력과 에러를 기록하는 로그파일로 지정하는 것이 바람직하다고 하겠다. 그래야 스크립트의 실행결과와 에러를 정확하게 파악하고 디버깅을 할 수 있기 때문이다.
이해가 안된다면 이해가 될때까지 반복해서 읽어보고 테스트를 해봐야 한다.
'OS > Linux&Unix' 카테고리의 다른 글
[Linux/Unix] stdin, stdout, stderr, and pipes (0) | 2022.12.27 |
---|---|
[Linux/Unix] find 조건 -exec ls -al {} \; (0) | 2022.12.23 |
[Linux/Unix] AIX - machine 전체 CPU 및 Core 수 확인 (0) | 2022.12.22 |
[Unix/Linux] nohup 사용하여 쉘스크립트 계속 실행하기 (0) | 2022.12.07 |
[Linux/Unix] grep 명령어 / -H, -v, -i, -E, -W (0) | 2022.12.02 |