이번 문서의 목표: 이 파일을 다 읽으면 표준입출력 번호와 재지정 기호를 표로 즉시 떠올리고, >·>>·2>·2>&1의 차이를 실행 결과로 설명하며, 파이프로 명령을 이어 붙이고 간단한 셸 스크립트를 만들어 실행할 수 있다.
명령은 어디서 입력을 받고 어디로 결과를 내보내는가
cat으로 파일을 열면 화면에 내용이 출력되고, grep으로 검색하면 화면에 결과가 뜬다. 이때 “화면”이라는 것은 사실 프로그램 입장에서는 그냥 하나의 목적지(destination)일 뿐이다. 리눅스의 모든 프로세스(process, 실행 중인 프로그램. 13편 참고)는 시작할 때 커널로부터 세 개의 통로를 기본으로 배정받는다. 이 통로를 파일 디스크립터(file descriptor, 프로세스가 열어 둔 입출력 대상을 가리키는 정수 번호표)라고 부른다.
쉽게 말하면: 모든 명령은 태어날 때부터 “입력받는 문 하나, 결과를 내보내는 문 두 개”를 손에 쥐고 시작한다.
정의와 번호
| 번호 | 이름(영문) | 한국어 | 기본 연결 대상 | 설명 |
|---|---|---|---|---|
0 | standard input | 표준입력 | 키보드 | 명령이 값을 읽어 들이는 입구 |
1 | standard output | 표준출력 | 화면(터미널) | 명령의 정상적인 결과가 나가는 출구 |
2 | standard error | 표준에러 | 화면(터미널) | 명령의 오류·경고 메시지가 나가는 출구 |
표준출력과 표준에러가 둘 다 기본으로는 같은 화면에 나가기 때문에, 평소에는 이 둘이 구분되어 있다는 사실이 잘 드러나지 않는다. 하지만 ls existing.txt nope.txt처럼 존재하는 파일과 존재하지 않는 파일을 함께 조회해 보면 차이가 드러난다. existing.txt에 대한 목록은 표준출력(1)으로, nope.txt: No such file or directory라는 오류는 표준에러(2)로 각각 나가는데, 둘 다 화면에 찍히다 보니 마치 한 줄기에서 나온 것처럼 보일 뿐이다. 재지정(redirection)을 배우면 이 둘을 실제로 갈라 서로 다른 곳으로 보낼 수 있다.
재지정 기호 정리
재지정(redirection)이란 프로세스의 입력 또는 출력이 연결되는 대상을 기본값(키보드, 화면)에서 다른 곳(주로 파일)으로 바꾸는 것을 말한다.
| 기호 | 이름 | 동작 |
|---|---|---|
명령 > 파일 | 출력 재지정(덮어쓰기) | 표준출력을 파일로 보낸다. 파일이 있으면 내용을 지우고 새로 쓴다 |
명령 >> 파일 | 출력 재지정(이어쓰기) | 표준출력을 파일 끝에 덧붙인다. 기존 내용은 보존된다 |
명령 2> 파일 | 에러만 재지정 | 표준에러만 파일로 보낸다. 정상 출력은 그대로 화면에 남는다 |
명령 2>> 파일 | 에러 이어쓰기 | 표준에러를 파일 끝에 덧붙인다 |
명령 < 파일 | 입력 재지정 | 키보드 대신 파일 내용을 표준입력으로 읽어 들인다 |
명령 > 파일 2>&1 | 출력과 에러를 함께 같은 파일로 | 표준출력을 파일로 보낸 뒤, 표준에러(2)를 표준출력(1)이 현재 가리키는 곳과 같은 곳으로 합친다 |
명령 &> 파일 | 출력과 에러를 함께(bash 확장 표기) | 위와 결과는 같으나 bash 전용의 더 짧은 표기다 |
>와 >>의 차이
$ echo "첫 줄" > memo.txt
$ cat memo.txt
첫 줄
$ echo "둘째 줄" > memo.txt
$ cat memo.txt
둘째 줄>를 두 번째에도 그대로 쓰면 파일이 통째로 비워지고 새 내용만 남는다. “첫 줄”은 사라진다. 이어서 쌓고 싶다면 >>를 써야 한다.
$ echo "첫 줄" > memo.txt
$ echo "둘째 줄" >> memo.txt
$ cat memo.txt
첫 줄
둘째 줄자주 틀리는 점: “> 리다이렉션은 파일 크기를 줄이지 못한다”는 식의 오답이 실제 모의고사에 등장한 적이 있다. 이것은 틀린 설명이다. >는 대상 파일을 0바이트로 자르는(truncate) 동작까지 포함한다. 다만 어떤 프로세스가 그 파일을 이미 열어 둔 채 계속 쓰고 있다면, 그 프로세스가 가진 파일 디스크립터의 위치는 그대로 남아 있어서 디스크 공간이 기대한 것처럼 즉시 줄어들지 않는 경우가 있다. 이는 재지정 기호 자체의 한계가 아니라, 열려 있는 프로세스와 관련된 별개의 현상이다.
에러만 따로 걸러내기
$ find / -name "*.conf" 2> /dev/null/dev/null은 리눅스가 제공하는 특수 장치 파일로, 여기로 보낸 내용은 그냥 사라진다(“블랙홀”에 비유된다). 위 명령은 권한이 없어 발생하는 Permission denied 같은 에러 메시지만 화면에서 지우고, 정상적으로 찾은 파일 목록(표준출력)은 그대로 화면에 보여 준다.
출력과 에러를 함께 한 파일로 모으기 — 순서가 중요하다
$ ls existing.txt nope.txt > out.log 2>&1이 명령을 한 단계씩 읽으면 다음과 같다.
> out.log: 표준출력(1)이 향할 곳을out.log파일로 바꾼다.2>&1: 표준에러(2)가 향할 곳을, 표준출력(1)이 지금 가리키고 있는 곳과 똑같이 맞춘다. 이 시점에 1은 이미out.log를 가리키므로, 2도out.log로 향하게 된다.
결과적으로 existing.txt에 대한 정상 목록과 nope.txt: No such file or directory라는 오류 메시지가 모두 out.log 한 파일에 기록된다.
만약 순서를 반대로 써서 ls existing.txt nope.txt 2>&1 > out.log라고 하면 결과가 달라진다.
2>&1: 이 시점에 표준출력(1)은 아직 화면을 가리키고 있으므로, 표준에러(2)도 화면을 가리키게 맞춘다.> out.log: 이제 표준출력(1)만out.log로 바꾼다. 표준에러(2)는 이미 1단계에서 화면으로 고정되었으므로 바뀌지 않는다.
결과는 정상 출력만 out.log에 들어가고, 오류 메시지는 화면에 그대로 남는다.
비유:
2>&1은 “2번 우편함을 1번 우편함이 지금 배달되는 그 주소로 맞춰라”는 지시다. 지시를 내리는 시점에 1번 우편함이 어디를 향하고 있었는지가 중요하다. 나중에 1번 주소를 또 바꾼다고 해서 2번이 따라 바뀌지는 않는다.
자주 틀리는 점: 2>&1을 2> &1처럼 띄어 쓰거나 2>1처럼 &를 빼고 쓰면 전혀 다른 뜻이 된다. & 없이 2>1이라고 쓰면 “표준에러를 1이라는 이름의 파일로 보내라”는 뜻이 되어 버린다. &가 붙어야 “1번이라는 파일 디스크립터를 가리켜라”는 뜻이 된다.
파이프 — 명령과 명령을 물려서 잇기
파이프(pipe, 기호 |) 는 한 명령의 표준출력을 다음 명령의 표준입력으로 곧바로 연결하는 기능이다. 재지정이 “명령과 파일”을 잇는다면, 파이프는 “명령과 명령”을 파일을 거치지 않고 바로 잇는다.
$ ps -ef | grep bashps -ef가 전체 프로세스 목록을 표준출력으로 쏟아내면, 그 내용이 화면에 찍히지 않고 곧바로 grep bash의 표준입력으로 들어간다. grep bash는 그 입력 중 “bash”라는 글자가 포함된 줄만 골라 화면에 출력한다.
파이프는 여러 개를 연달아 이어 붙일 수 있다.
$ cat access.log | grep "ERROR" | sort | uniq -c | sort -nr이 한 줄은 다음 순서로 동작한다.
cat access.log— 로그 파일 내용을 그대로 흘려보낸다.grep "ERROR"— “ERROR”가 들어간 줄만 남긴다.sort— 줄들을 알파벳(문자) 순서로 정렬한다.uniq로 중복을 세려면 같은 줄끼리 먼저 붙어 있어야 하기 때문이다.uniq -c— 연속된 중복 줄의 개수를 세어 앞에 붙인다.sort -nr— 앞에 붙은 숫자를 기준으로 내림차순(큰 수부터) 정렬한다.
쉽게 말하면: 파이프는 여러 명령을 공장의 컨베이어 벨트처럼 이어서, 앞 공정의 결과물이 곧바로 다음 공정의 재료가 되게 한다.
재지정과 파이프의 차이
| 구분 | 연결 대상 | 기호 |
|---|---|---|
| 재지정 | 명령 ↔ 파일(또는 /dev/null 같은 장치) | >, >>, <, 2> |
| 파이프 | 명령 ↔ 다른 명령 | | |
자주 틀리는 점: 파이프로 이어진 명령들은 사실 각각 독립된 프로세스로, 동시에 실행되며 중간 데이터가 메모리 버퍼를 통해 흐른다. “파이프는 앞 명령이 완전히 끝난 뒤 결과 파일을 만들고 그것을 다음 명령이 읽는 것”이라고 착각하기 쉬운데, 파일을 거치지 않는다는 점이 재지정과의 핵심 차이다.
간단한 셸 스크립트 작성과 실행
셸 스크립트(shell script)는 여러 명령을 순서대로 적어 둔 텍스트 파일이다. 매번 명령을 하나씩 입력하는 대신, 파일 하나를 실행해 한꺼번에 처리한다.
-
파일 만들기
$ vi hello.sh(vi 사용법은 15편 참고)
-
셔뱅(shebang)으로 시작하기
#!/bin/bash echo "오늘은 $(date +%Y-%m-%d) 입니다" echo "현재 위치: $(pwd)"첫 줄의
#!/bin/bash를 셔뱅 또는 해시뱅(hashbang)이라 부른다.#(hash, 우물 정)과!(bang, 느낌표)를 합친 이름이다. 이 줄은 “이 스크립트를 실행할 때/bin/bash프로그램을 통역기로 써라”라고 커널에 알려 주는 표시다. 셔뱅이 없으면 현재 사용 중인 셸의 문법으로 해석을 시도하므로, 스크립트가 bash 문법을 쓴다면 셔뱅을 반드시 첫 줄에 명시하는 것이 안전하다. -
실행 권한 주기
새로 만든 텍스트 파일은 기본적으로 실행 권한이 없다(권한 개념은 11편 참고).
ls -l hello.sh로 확인하면-rw-r--r--처럼x가 빠져 있는 것을 볼 수 있다. 실행 파일로 쓰려면 실행 권한을 추가해야 한다.$ chmod +x hello.sh $ ls -l hello.sh -rwxr-xr-x 1 ihduser ihduser 58 9월 6 10:00 hello.sh -
실행하기
$ ./hello.sh 오늘은 2026-09-06 입니다 현재 위치: /home/ihduser파일명 앞에
./를 붙인 이유가 중요하다. 19편에서 다뤘듯 셸은 명령을 입력하면PATH에 등록된 디렉터리만 순서대로 뒤진다. 현재 디렉터리(.)는 보안상의 이유로 보통PATH에 포함되어 있지 않으므로,hello.sh라고만 입력하면 “명령을 찾을 수 없다”는 오류가 난다../를 붙이면 “현재 디렉터리에 있는 이 파일을 상대경로로 직접 가리켜 실행하라”는 뜻이 되어,PATH탐색을 거치지 않고 확실하게 실행된다.
실행하는 또 다른 방법
$ bash hello.sh
$ sh hello.sh이렇게 bash나 sh 명령 뒤에 스크립트 파일명을 인자로 주면, 파일 자체에 실행 권한(x)이 없어도 실행할 수 있다. 이 경우 셔뱅 줄은 사실상 무시되고, 명령줄에서 지정한 통역기(bash 또는 sh)가 그대로 쓰인다.
자주 틀리는 점
- 스크립트 파일에 읽기 권한(
r)만 있고 실행 권한(x)이 없는 상태에서./스크립트로 실행을 시도하면 “Permission denied”가 뜬다. 반대로 실행 권한은 있는데 읽기 권한이 없으면 통역기가 파일 내용을 읽지 못해 역시 실행에 실패한다. 셸 스크립트는 실행 권한과 읽기 권한이 함께 있어야 정상 동작한다. - 셔뱅 줄은 반드시 파일의 첫 줄, 첫 칸부터 시작해야 한다. 앞에 빈 줄이나 공백이 있으면 셔뱅으로 인식되지 않는다.
직접 해보기
00편에서 만든 rocky 컨테이너에서 진행합니다. 아직 안 만들었다면 00편을 먼저 보세요.
표준출력과 표준에러가 별개의 통로라는 것을 직접 확인합니다.
touch real.txt
ls real.txt fake.txt
ls real.txt fake.txt > out.log
cat out.log
ls real.txt fake.txt > out.log 2> err.log
ls real.txt fake.txt > out.log 2>&1무엇을 보아야 하나: 두 번째 줄은 정상 목록과 오류 메시지가 화면에 섞여 나온다. 세 번째 줄처럼 표준출력만 out.log로 보내면, 오류 메시지(fake.txt: No such file or directory)는 화면에 그대로 남는 것을 확인한다. cat out.log에는 real.txt 줄만 들어 있다. 네 번째·다섯 번째 줄로 오류를 따로 또는 함께 파일로 보내는 차이도 비교한다.
이어서 파이프와 스크립트 실행 권한을 확인합니다.
ls -l /etc | grep conf | wc -l
echo 'echo hi' > try.sh
./try.sh
chmod +x try.sh
./try.sh./try.sh는 처음에는 실행 권한이 없어 “Permission denied”가 뜨고, chmod +x 뒤에는 정상 실행된다.
무엇을 보아야 하나: wc -l(word count, 줄 수 세기)로 파이프를 이은 결과가 숫자 하나로 나오는지, 그리고 권한 없는 스크립트가 실행되지 않다가 chmod +x 후 실행되는 순간의 차이를 확인한다.
왜 이걸 해보나: “표준출력만 파일로 보내면 오류도 함께 사라진다”는 오해와, 실행 권한 없이도 스크립트가 돌아갈 것이라는 오해가 시험에서 실행 결과 예측 문제로 자주 나온다.
핵심 정리
- 표준입력은
0, 표준출력은1, 표준에러는2다. 평소 표준출력과 표준에러가 같은 화면에 나와 구분되지 않을 뿐, 서로 다른 통로다. >는 덮어쓰기,>>는 이어쓰기다.2>는 에러만,2>&1은 에러를 출력이 가리키는 곳으로 합친다.2>&1은 반드시>뒤에 와야 같은 파일로 모인다.- 파이프(
|)는 명령과 명령을 파일 없이 바로 연결하며, 재지정은 명령과 파일(또는 장치)을 연결한다. - 셸 스크립트는 셔뱅으로 통역기를 지정하고,
chmod +x로 실행 권한을 준 뒤./파일명으로 실행한다.PATH에 현재 디렉터리가 없어./가 필요하다.