리다이렉션
고친 사람 github-actions[bot]
리다이렉션은 두 분야에서 서로 다른 일을 가리키는 말입니다. 명령을 받아 실행해 주는 프로그램인 셸에서는 명령이 읽고 쓰는 곳을 실행 직전에 바꿔 주는 기능입니다. 웹에서는 서버가 요청을 다른 주소로 다시 보내라고 돌려주는 응답입니다. 이름만 같고 하는 일은 따로입니다.
쉽고 빠른 이해
무슨 일을 하나 — 셸에서는 화면에 나갈 출력을 파일로 돌립니다. ls > files.txt 라고 치면 파일 목록이 화면 대신 files.txt 에 적힙니다. 웹에서는 옛 주소로 온 요청에 「새 주소로 다시 요청하라」고 답합니다.
왜 이렇게 하나 — 셸의 리다이렉션 덕분에 프로그램은 결과를 어디에 둘지 몰라도 됩니다. 웹의 리다이렉션 덕분에 주소가 바뀌어도 옛 주소로 온 요청을 새 주소로 이어 줄 수 있습니다.
어떻게 도나
- 셸은 명령을 실행하기 직전에 기호가 가리키는 파일을 엽니다. 명령이 내보내는 출력은 그 파일로 갑니다
- 웹 서버는 300번대 응답에 새 주소를 실어 보냅니다
- 브라우저는 그 새 주소로 요청을 한 번 더 보냅니다
대가 — 셸에서는 > 가 파일을 먼저 비우므로 잘못 치면 내용이 사라집니다. 기호를 적는 순서도 결과를 바꿉니다. 웹에서는 요청이 한 번 더 오갑니다. 300번대 번호에 따라서는 두 번째 요청이 첫 요청과 다르게 나가기도 합니다.
상세
리다이렉션이라는 말은 두 분야에서 따로 정의됩니다. 하나는 셸입니다. 다른 하나는 웹 브라우저와 서버가 주고받는 규칙인 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)입니다. 두 뜻은 정의하는 문서도, 바꾸는 대상도, 일을 하는 주체도 다릅니다. 둘이 함께 가진 것은 「다른 곳으로 돌린다」는 영어 낱말 redirect 의 뜻뿐입니다.
셸의 리다이렉션은 명령이 읽고 쓰는 곳을 그 명령이 실행되기 전에 바꾸는 문법입니다. ls > files.txt 라고 치면 파일 목록이 화면 대신 files.txt 에 적힙니다. > files.txt 는 ls 에게 인자로 넘어가지 않습니다. 셸이 읽어서 처리합니다. ls 는 평소처럼 출력할 뿐입니다.
셸이 바꾸는 것은 파일 디스크립터가 가리키는 파일입니다. 파일 디스크립터는 프로그램이 파일을 열 때 운영체제에게서 받는 작은 정수입니다. 그 뒤로 읽기와 쓰기를 부탁할 때는 파일 이름 대신 이 번호를 건넵니다.
이렇게 나눠 둔 덕분에 프로그램은 결과를 어디에 둘지 몰라도 됩니다. 셸에서 불릴 것을 염두에 둔 프로그램은 파일을 스스로 열지 않게 짜이기도 합니다. 결과를 파일로 보낼지 다른 명령으로 넘길지는 명령을 치는 사람이 정합니다.
HTTP 의 리다이렉션은 300번대 상태 코드 묶음의 이름입니다. 상태 코드는 서버가 응답마다 붙여 결과를 알리는 세 자리 숫자입니다. 300번대는 요청을 마치려면 클라이언트가 무언가를 더 해야 한다는 뜻입니다. 클라이언트는 요청을 보내는 프로그램입니다. 브라우저가 대표입니다.
대표적인 경우는 요청한 자원이 다른 주소로 옮겨 간 것입니다. 서버가 새 주소를 알려 주면 클라이언트가 그 주소로 요청을 다시 보냅니다. 이 응답 덕분에 옛 주소로 온 요청을 새 주소로 이어 줄 수 있습니다. 이 응답 하나하나는 리다이렉트(redirect)라고도 부릅니다.
두 뜻은 동작이 겹치지 않습니다. 셸에서는 셸 혼자 번호가 가리키는 파일을 바꿉니다. 주고받는 상대가 없습니다. HTTP 에서는 서버가 알리고 클라이언트가 새 요청을 보냅니다. 요청과 응답이 한 번 더 오갑니다.
맥락별 뜻
셸의 뜻은 유닉스 계열 운영체제가 함께 따르는 규격인 POSIX(Portable Operating System Interface)가 정합니다. HTTP 의 뜻은 인터넷 표준 문서인 RFC(Request for Comments) 9110 이 정합니다. 아래 표는 두 뜻을 그 뜻을 적은 문서와 함께 놓은 것입니다.
| 맥락 | 뜻 | 출처 |
|---|---|---|
| 셸 | 명령에 대해 파일을 열거나 닫는 문법. 한 파일 디스크립터를 다른 번호의 복제로 만들기도 한다. 명령이 실행되기 전에 처리된다 | POSIX.1-2024 Shell Command Language §2.7 Redirection · GNU Bash Reference Manual §3.6 Redirections |
| HTTP | 요청을 마치려면 클라이언트가 추가 동작을 해야 한다고 알리는 300번대 상태 코드 묶음 | RFC 9110 §15.4 Redirection 3xx · §10.2.2 Location |
셸
명령이 시작될 때 파일 디스크립터 0번, 1번, 2번은 이미 열려 있습니다. 이 셋이 명령의 입출력 번호입니다. 쓰임이 정해져 있어서 프로그램은 따로 열지 않고 바로 씁니다.
| 번호 | 이름 | 쓰임 | 평소에 가리키는 것 |
|---|---|---|---|
| 0 | 표준 입력 | 읽어 들이는 입력 | 키보드 |
| 1 | 표준 출력 | 정상 결과 | 터미널 화면 |
| 2 | 표준 오류 | 오류 메시지 같은 진단 출력 | 터미널 화면 |
정상 결과와 오류는 둘 다 화면으로 가서 한데 섞여 보입니다. 키보드와 화면은 터미널 장치입니다. 터미널 장치도 파일로 다뤄집니다. 그래서 위 표의 「가리키는 것」과 아래에서 말하는 「파일」은 같은 것을 가리킵니다.
리다이렉션은 번호는 손대지 않고 그 번호가 가리키는 것만 바꿉니다. 아래 그림은 ls > files.txt 를 실행할 때 번호 셋이 어디를 가리키는지 보입니다. 번호 쪽은 그대로이고 화살표 끝만 바뀝니다.
flowchart TD
subgraph 평소
P0["0번"] --> PK["키보드"]
P1["1번"] --> PS["화면"]
P2["2번"] --> PS2["화면"]
end
subgraph 뒤["리다이렉션 뒤"]
Q0["0번"] --> QK["키보드"]
Q1["1번"] --> QF["files.txt"]
Q2["2번"] --> QS["화면"]
end
리다이렉션의 꼴은 [n]기호 낱말 입니다. 앞의 숫자 n 이 바꿀 파일 디스크립터 번호입니다. 숫자와 기호 사이에는 빈칸을 둘 수 없습니다. 숫자를 생략하면 < 로 시작하는 기호는 0번, > 로 시작하는 기호는 1번을 뜻합니다.
n 에 적을 수 있는 번호의 상한은 셸마다 다릅니다. 어느 셸에서나 0번부터 9번까지는 적을 수 있습니다. 숫자와 기호와 낱말은 명령에 인자로 넘어가지 않습니다.
| 기호 | 하는 일 |
|---|---|
< 파일 |
0번이 파일을 읽게 엽니다 |
> 파일 |
1번이 파일에 쓰게 엽니다. 파일이 없으면 빈 파일을 만듭니다. 있는 파일이 일반 파일이면 길이를 0 으로 비웁니다 |
>| 파일 |
> 와 같습니다. 덮어쓰기를 막는 설정이 켜져 있어도 엽니다 |
>> 파일 |
1번이 파일 끝에 덧붙이게 엽니다. 파일이 없으면 만듭니다 |
<> 파일 |
0번이 파일을 읽기와 쓰기 둘 다로 엽니다 |
2>&1 |
2번을 1번의 복제로 만듭니다. 2번이 지금 1번이 가리키는 파일을 같이 가리킵니다 |
2>&- |
2번을 닫습니다 |
<< EOF |
다음 줄부터 EOF 만 적힌 줄 앞까지를 0번으로 넣습니다 |
마지막 줄을 히어 문서라고 부릅니다. << 뒤의 낱말은 끝을 알리는 표시라 아무 낱말이나 됩니다. 흔히 EOF(End Of File, 파일 끝)를 씁니다.
> 와 >> 의 차이에서 사고가 잦습니다. 아래는 같은 파일에 세 번 쓴 결과입니다. 줄마다 오른쪽 주석이 그 줄을 실행한 뒤 파일에 남은 내용입니다.
echo hi > a.txt # a.txt: hi
echo yo >> a.txt # a.txt: hi 다음 줄 yo
echo ok > a.txt # a.txt: ok
셋째 줄의 > 가 파일을 비운 뒤 썼습니다. 앞의 두 줄은 사라졌습니다. 로그 파일처럼 계속 쌓아 가야 하는 파일에는 >> 를 씁니다.
입력도 같은 식으로 바꿉니다. wc -l 은 줄 수를 세는 명령입니다. 아래 두 줄은 같은 파일의 줄 수를 셉니다.
wc -l a.txt
wc -l < a.txt
첫 줄은 a.txt 가 wc 의 인자로 넘어갑니다. wc 가 그 이름으로 파일을 직접 엽니다. 둘째 줄은 셸이 파일을 열어 0번에 잇습니다. wc 는 파일 이름을 받지 못하고 0번에서 읽기만 합니다.
정상 결과와 오류를 가르고 싶으면 2번을 따로 돌립니다. 아래 명령은 결과를 out.log 에, 오류를 err.log 에 담습니다.
./build.sh > out.log 2> err.log
둘을 한 파일에 모으려면 2>&1 을 씁니다. &1 은 「1번 파일 디스크립터」라는 뜻입니다. 다음 명령은 결과와 오류를 모두 all.log 에 담습니다.
./build.sh > all.log 2>&1
두 기호를 적은 순서가 결과를 바꿉니다. 그 까닭은 「동작」 절에서 봅니다.
버리고 싶은 출력은 /dev/null 로 보냅니다. 널 장치라고 부르는 특수 파일이라 여기에 쓴 것은 어디에도 남지 않습니다.
리다이렉션은 보통 명령 하나에만 걸립니다. exec 와 함께 쓸 때는 다릅니다. exec 는 밖의 프로그램을 띄우지 않고 셸이 직접 처리하는 내장 명령입니다.
exec > log.txt 처럼 뒤에 명령 없이 기호만 적으면 그 리다이렉션은 셸 자신의 실행 환경에 걸립니다. 셸 자신의 1번이 log.txt 로 바뀝니다. 그 뒤로 셸에서 실행하는 명령의 출력이 모두 거기로 갑니다.
exec 라는 이름은 시스템 콜에도 붙어 있습니다. 그쪽은 「동작」 절에서 나옵니다.
HTTP
300번대 묶음 안에도 종류가 넷 있습니다.
| 종류 | 코드 |
|---|---|
| 요청한 자원이 다른 주소에 있을 수 있다 | 301 · 302 · 307 · 308 |
| 이 자원을 나타낼 수 있는 후보 여럿 중에서 고르게 한다 | 300 |
| 원래 요청에 대한 간접 응답이 되는 다른 자원으로 보낸다 | 303 |
| 클라이언트가 전에 저장해 둔 결과로 보낸다 | 304 |
새 주소는 Location 헤더에 실립니다. Location 헤더는 응답에 붙어 어떤 자원을 가리키는 칸입니다. 300번대 응답에서는 클라이언트가 자동으로 따라갈 대상 주소를 가리킵니다. 주소가 상대 경로로 적혀 있으면 원래 요청한 주소를 기준으로 풀어서 완성합니다.
요청 방식은 그 요청이 무엇을 하려는지 밝히는 낱말입니다. HTTP 에서는 메서드라고 부릅니다.
GET 은 자원을 읽어 오는 요청 방식입니다. POST 는 데이터를 실어 보내는 요청 방식입니다. HEAD 는 GET 과 같되 응답 본문 없이 헤더만 받는 요청 방식입니다.
Location 헤더가 있으면 클라이언트는 그 주소로 요청을 자동으로 다시 보내도 됩니다(MAY). 받은 300번대 번호가 클라이언트가 모르는 번호여도 그렇습니다. 자동으로 다시 보낼 때는 원래 요청을 고쳐 보내는 것이 좋습니다(SHOULD). 대상 주소는 새 주소로 바꿉니다. 상태 코드의 뜻에 따라서는 요청 방식도 바꿉니다.
다음은 http://www.example.org/~tim 을 GET 으로 요청했을 때 303 응답이 돌아온 경우입니다.
sequenceDiagram
participant 클라이언트
participant 서버
클라이언트->>서버: GET /~tim
서버-->>클라이언트: 303 See Other · Location: /People.html#tim
클라이언트->>서버: GET /People.html
Note over 클라이언트: 새 주소의 결과를 원래 요청의 답으로 보인다
Location 값 /People.html#tim 은 상대 경로입니다. 클라이언트는 원래 주소를 기준으로 풀어 http://www.example.org/People.html#tim 으로 갑니다.
#tim 은 프래그먼트입니다. 딸린 자원을 간접으로 가리키는 꼬리표입니다. 클라이언트 쪽 처리에만 쓰입니다.
요청 줄에 실리는 대상 주소에서는 프래그먼트가 빠집니다. 위 그림의 둘째 요청이 GET /People.html 인 까닭입니다.
Location 값에 프래그먼트가 없으면 클라이언트는 원래 주소에 있던 프래그먼트를 물려받은 것처럼 처리해야 합니다(MUST). http://www.example.org/index.html#larry 를 요청해 Location: http://www.example.net/index.html 을 받으면 #larry 를 그대로 붙인 주소로 갑니다.
요청과 응답이 한 번 더 오갔습니다. 셸의 리다이렉션에는 없는 왕복입니다.
다섯 코드는 두 축으로 갈립니다. 옮겨 간 것이 영구인가 임시인가, 그리고 다시 보내는 요청의 방식을 바꿔도 되는가입니다.
| 코드 | 이름 | 무엇을 알리나 | 다시 보내는 요청의 방식 |
|---|---|---|---|
| 301 | Moved Permanently | 영구 주소가 새로 정해졌다. 앞으로는 새 주소를 쓴다 | POST 를 GET 으로 바꿔도 된다(MAY) |
| 302 | Found | 임시로 다른 주소에 있다. 앞으로도 원래 주소를 쓴다 | POST 를 GET 으로 바꿔도 된다(MAY) |
| 303 | See Other | 다른 자원이 간접 응답을 준다 | GET 이나 HEAD 로 가져온다 |
| 307 | Temporary Redirect | 임시로 다른 주소에 있다. 앞으로도 원래 주소를 쓴다 | 바꾸면 안 된다(MUST NOT) |
| 308 | Permanent Redirect | 영구 주소가 새로 정해졌다. 앞으로는 새 주소를 쓴다 | 지키는 쪽으로 정해졌다 |
303 은 POST 의 결과를 다른 자원으로 보낼 때 주로 씁니다. POST 응답에 담길 내용을 가리키는 주소를 Location 에 실어 보내는 것이 그런 경우입니다. 그러면 그 내용이 따로 가리킬 수 있는 자원이 됩니다. 주소로 다시 찾아가거나 북마크하거나 캐시에 담을 수 있습니다.
OAuth 2.0 은 사용자가 가진 자원을 다른 프로그램이 쓸 수 있게 권한을 내주는 절차를 정한 규격입니다. 그 절차에도 HTTP 리다이렉션이 나옵니다.
권한을 내주는 쪽을 인가 서버라고 부릅니다. 권한을 받아 쓰는 프로그램은 OAuth 규격에서 클라이언트라고 부릅니다. 이 절에서 줄곧 클라이언트라고 부른 요청 보내는 쪽과는 다른 것을 가리키는 말입니다.
사용자가 권한 허락을 마치면 인가 서버가 사용자의 브라우저를 그 프로그램으로 돌려보냅니다. 그때 브라우저가 가는 주소가 리다이렉션 엔드포인트입니다. 엔드포인트는 요청을 받아 주는 주소 하나를 가리키는 말입니다.
동작
이 절은 셸 맥락만 다룹니다. 셸이 명령 줄 하나에 붙은 리다이렉션을 어떤 순서로 처리하는지 봅니다. 대상은 ls > dirlist 2>&1 과 sort names.txt > sorted.txt 두 줄입니다.
결과부터 적으면 셋입니다.
- 리다이렉션은 적힌 순서대로 왼쪽부터 처리됩니다
- 명령을 파이프로 이었으면 파이프 연결이 리다이렉션보다 먼저입니다
- 리다이렉션은 명령이 실행되기 전에 처리됩니다. 내장 명령이 아닌 명령에 붙은 리다이렉션은 셸 자신의 번호를 바꾸지 않습니다
왼쪽부터 처리하는 차례
2>&1 은 2번을 「그 순간」 1번의 복제로 만듭니다. 그 뒤에 1번이 다른 파일로 바뀌어도 2번은 따라가지 않습니다. 그래서 같은 기호 둘도 순서가 바뀌면 결과가 달라집니다.
아래 표는 ls > dirlist 2>&1 을 한 단계씩 따라갑니다. 칸마다 그 단계가 끝난 뒤 1번과 2번이 가리키는 곳입니다.
| 단계 | 1번 | 2번 |
|---|---|---|
| 시작 | 화면 | 화면 |
> dirlist |
dirlist | 화면 |
2>&1 |
dirlist | dirlist |
1번을 먼저 파일로 돌린 뒤 2번이 그것을 복제했습니다. 결과와 오류가 모두 dirlist 로 갑니다.
이번에는 두 기호를 뒤집은 ls 2>&1 > dirlist 입니다.
| 단계 | 1번 | 2번 |
|---|---|---|
| 시작 | 화면 | 화면 |
2>&1 |
화면 | 화면 |
> dirlist |
dirlist | 화면 |
2번은 아직 화면을 가리키던 1번을 복제했습니다. 그 뒤 1번만 파일로 바뀌었습니다. 오류는 화면에 남습니다.
파이프와 함께 쓸 때
파이프는 한 명령의 1번을 다음 명령의 0번에 잇는 연결입니다. | 기호로 명령을 이은 줄을 파이프라인이라고 부릅니다. 리다이렉션이 번호를 파일에 잇는다면 파이프는 번호를 다른 명령에 잇습니다.
둘을 섞으면 파이프 연결이 먼저 이뤄집니다. 각 명령에 적힌 리다이렉션은 그 뒤에 처리됩니다. grep error 는 입력에서 error 가 든 줄만 골라 찍는 명령입니다. 아래 명령은 오류 출력까지 grep 으로 넘깁니다.
./build.sh 2>&1 | grep error
파이프가 ./build.sh 의 1번을 먼저 grep 에 이어 두었습니다. 그다음 2>&1 이 그 1번을 복제했습니다. 그래서 2번으로 나간 오류도 grep 으로 흘러갑니다.
셸과 떨어진 실행 환경
이 소절은 sort names.txt > sorted.txt 한 줄로 셸 밑에서 일어나는 일을 봅니다.
프로세스는 실행 중인 프로그램 하나입니다. 셸도 프로세스입니다. sort 도 실행되면 프로세스가 됩니다.
셸은 내장 명령이 아닌 명령을 셸과 떨어진 별도의 실행 환경에서 실행합니다. 그 환경은 흔히 셸을 복제해 만든 또 하나의 프로세스입니다. 복제해 만든 쪽을 자식이라고 부릅니다. 복제한 쪽은 부모입니다.
별도 환경의 열린 파일은 셸이 가진 열린 파일에 그 명령의 리다이렉션을 더한 것입니다. 열린 파일은 번호가 가리키는 대상을 말합니다. 명령은 셸 자신의 환경을 바꾸지 못합니다.
커널은 운영체제의 핵심부입니다. 파일을 열거나 프로세스를 만드는 일은 커널이 맡습니다.
시스템 콜은 프로그램이 커널에 일을 부탁하는 정해진 호출입니다. 리다이렉션 하나를 꾸미는 데 쓰이는 호출은 다섯입니다.
표의 exec 는 그 시스템 콜 가운데 하나입니다. 「맥락별 뜻」에서 본 exec 는 셸 내장 명령이었습니다. 같은 이름이 두 곳에 쓰입니다.
| 호출 | 하는 일 |
|---|---|
| fork | 지금 프로세스를 복제해 자식을 만듭니다. 자식은 부모의 파일 디스크립터를 복사해 받습니다. 각 번호는 부모의 같은 번호와 같은 열린 파일을 가리킵니다. FD_CLOFORK 가 켜진 번호는 복사되지 않습니다 |
open |
파일을 열고 번호를 줍니다. 그때 열려 있지 않은 번호 중 가장 작은 것을 줍니다. > 는 일반 파일이면 길이를 0 으로 비우는 옵션을 붙여 엽니다 |
| dup2 | dup2(a, b) 는 b 가 a 와 같은 열린 파일을 가리키게 합니다. b 가 열려 있었으면 먼저 닫습니다. a 와 b 가 같은 번호면 닫지 않습니다 |
close |
번호를 닫습니다 |
| exec | 프로세스에서 도는 프로그램을 새 프로그램으로 바꿉니다. 열려 있던 번호는 새 프로그램에서도 열린 채 남습니다. FD_CLOEXEC 가 켜진 번호는 닫힙니다 |
이 다섯으로 sort names.txt > sorted.txt 를 꾸미면 아래 차례가 됩니다. 칸은 그 단계가 끝난 뒤 자식의 번호가 가리키는 곳입니다.
| 단계 | 1번 | 3번 |
|---|---|---|
fork 직후 |
화면 | 없음 |
open("sorted.txt") |
화면 | sorted.txt |
dup2(3, 1) |
sorted.txt | sorted.txt |
close(3) |
sorted.txt | 없음 |
exec("sort") |
sorted.txt | 없음 |
open 이 3번을 받은 것은 0번부터 2번까지가 이미 열려 있어서입니다. exec 로 프로그램이 sort 로 바뀌어도 1번은 sorted.txt 를 가리킨 채 남습니다. 그래서 sort 는 평소처럼 1번에 쓸 뿐입니다. 그런데도 결과가 파일로 갑니다.
이 일은 모두 자식 안에서 일어납니다. 셸 자신의 1번은 계속 화면을 가리킵니다. 명령이 끝나면 다음 프롬프트가 화면에 뜹니다.
flowchart TD
subgraph 셸
S1["1번"] --> SS["화면"]
end
subgraph 자식["자식 · sort"]
C1["1번"] --> CF["sorted.txt"]
end
호출 하나하나가 하는 일은 표준 문서에 적힌 것입니다. 셸이 이 다섯을 꼭 이 차례로 부르는지를 적은 표준 문서는 없습니다(미확인).
실패
이 절은 리다이렉션이 깨지는 조건을 모읍니다. 리다이렉션이 성공해도 원하는 결과가 안 나오는 조건도 함께 둡니다. 표의 앞 다섯 줄은 셸 맥락입니다. 뒤의 두 줄은 HTTP 맥락입니다.
| 맥락 | 조건 | 일어나는 일 |
|---|---|---|
| 셸 | 읽는 파일을 > 로 같은 명령에서 씀 |
명령이 읽기도 전에 파일이 비워진다 |
| 셸 | 덮어쓰기 막기 설정이 켜진 채 있는 파일에 > |
리다이렉션이 실패한다. 셸이 진단 메시지를 쓴다 |
| 셸 | 2>&1 을 > 앞에 씀 · & 를 빠뜨림 |
오류가 화면에 남는다 · 엉뚱한 파일이 생긴다 |
| 셸 | sudo 명령 > 파일 로 관리자 전용 파일에 씀 |
파일을 여는 셸에 권한이 없어 리다이렉션이 실패한다. 셸이 진단 메시지를 쓴다 |
| 셸 | 출력을 파일로 돌림 | 출력이 늦게 보일 수 있다. 비정상 종료 때는 끝부분이 안 남을 수 있다 |
| HTTP | 리다이렉션이 서로를 가리킴 | 주소들이 고리를 이룬다. 클라이언트가 알아채 끊어야 한다(SHOULD) |
| HTTP | POST 요청이 301 · 302 를 받음 | 다시 보내는 요청이 GET 으로 바뀔 수 있다 |
셸에서는 파일을 열거나 만들지 못하면 그 리다이렉션이 실패합니다. 열려 있지 않은 번호를 >& 로 복제하려 할 때도 리다이렉션 오류가 납니다. 「읽는 파일에 쓰기」와 「파일로 보낸 출력이 늦게 보일 때」는 리다이렉션이 성공하고도 원하는 결과가 안 나오는 경우입니다.
진단 메시지와 종료 상태
리다이렉션이 실패하면 셸은 표준 오류에 진단 메시지를 씁니다. 셸이 거기서 끝나는지는 그 리다이렉션이 어디에 붙어 있었는지에 따라 갈립니다.
| 리다이렉션이 붙은 곳 | 대화형이 아닌 셸 | 대화형 셸 |
|---|---|---|
| 특수 내장 명령 | 끝난다 | 끝나지 않는다 |
| 복합 명령 | 끝나지 않는다 | 끝나지 않는다 |
| 함수 실행 | 끝나지 않는다 | 끝나지 않는다 |
| 그 밖의 유틸리티 | 끝나지 않는다 | 끝나지 않는다 |
특수 내장 명령은 셸이 직접 처리하는 명령 가운데 exec · set 처럼 표준이 따로 묶어 둔 것들입니다. 대화형 셸은 네 경우 모두 끝나지 않습니다. 다만 오류가 난 그 명령은 더 처리하지 않습니다.
명령 이름 없이 리다이렉션만 적은 줄은 리다이렉션이 실패하면 0 보다 큰 종료 상태로 바로 실패합니다. 명령 이름이 있을 때 그 명령이 아예 실행되지 않는다고 못박은 표준 문서는 없습니다(미확인).
종료 상태 값도 경우마다 갈립니다. 특수 내장 exec 에 붙은 리다이렉션이 실패하면 1 에서 125 사이의 값입니다. bash 는 리다이렉션 오류가 난 명령의 종료 상태를 0 보다 큰 값으로 둡니다. 보통 명령의 종료 상태 값을 못박은 표준 문서는 없습니다(미확인).
읽는 파일에 쓰기
sort a.txt > a.txt 는 파일을 정렬해 같은 이름으로 되돌려 두려는 명령처럼 보입니다. 끝나고 나면 a.txt 는 비어 있습니다.
까닭은 순서입니다. 셸은 sort 를 실행하기 전에 리다이렉션부터 처리합니다. > 는 이미 있는 파일을 길이 0 으로 비우며 엽니다. sort 가 a.txt 를 읽기 시작할 때는 읽을 것이 남아 있지 않습니다.
덮어쓰기 막기 설정
셸에는 > 가 이미 있는 파일을 덮어쓰지 못하게 막는 설정이 있습니다. 이름은 noclobber 이고 set -C 로 켭니다.
켜 두면 > 로 열려는 파일이 이미 있는 일반 파일일 때 리다이렉션이 실패합니다. 다른 파일을 가리키는 특수 파일인 심볼릭 링크가 일반 파일을 가리켜도 실패합니다.
그래도 덮어써야 하는 파일 하나에는 이 설정을 넘어 파일을 여는 >| 를 씁니다. >| 는 또 다른 셸인 KornShell 에서 들여온 기호입니다. 설정이 모든 파일을 한꺼번에 막고 >| 가 한 파일씩 예외를 내주는 식입니다. 두 장치가 짝을 이뤄 실수로 덮어쓰는 것을 막습니다.
기호 순서와 오타
로그 파일에 오류가 안 보이면 2>&1 의 순서부터 봅니다. 명령 2>&1 > out.log 는 결과만 파일로 보냅니다. 오류는 화면에 남습니다. 단계별 표는 「동작」 절의 「왼쪽부터 처리하는 차례」에 있습니다.
& 를 빠뜨려 2>1 이라고 치면 셸은 1 을 파일 이름으로 읽습니다. 리다이렉션은 성공합니다. 오류는 1 이라는 파일에 담깁니다.
>& 뒤의 숫자가 열려 있지 않은 번호면 리다이렉션 오류가 납니다. 3번을 연 적이 없는 채로 2>&3 이라고 적으면 이 경우입니다.
sudo 로 실행한 명령의 리다이렉션
sudo 는 명령 하나를 관리자 권한으로 실행해 주는 도구입니다. 그런데 sudo echo x > /etc/app.conf 는 실패합니다. 관리자 권한으로 도는 것은 echo 뿐입니다. > /etc/app.conf 를 처리해 파일을 여는 것은 명령 줄을 해석한 사용자의 셸입니다.
일반 사용자 권한인 셸은 그 파일을 쓰기로 열지 못합니다. 그래서 리다이렉션이 실패합니다. 셸은 표준 오류에 진단 메시지를 씁니다.
리다이렉션까지 관리자 권한으로 하려면 리다이렉션을 처리할 셸째로 sudo 아래에서 띄웁니다. 아래는 /home 아래 디렉터리들의 사용량을 정렬해 USAGE 파일에 담는 예입니다.
sudo sh -c "cd /home ; du -s * | sort -rn > USAGE"
sh -c 는 따옴표 안의 명령 줄을 새 셸이 실행하게 합니다. 그 새 셸이 관리자 권한으로 돌기 때문에 > USAGE 도 관리자 권한으로 열립니다.
tee 를 쓰는 길도 있습니다. tee 는 0번으로 받은 것을 1번에 베낍니다. 같은 내용을 파일에도 씁니다. tee 를 sudo 로 띄우면 파일을 여는 프로세스가 관리자 권한으로 도는 tee 가 됩니다.
파일로 보낸 출력이 늦게 보일 때
터미널에서는 바로바로 보이던 출력을 파일로 보내면 한참 뒤에 몰려 나오기도 합니다. 프로그램이 1번이 터미널을 가리키는지에 따라 내보내는 방식을 바꾸기 때문입니다.
많은 프로그램은 출력을 곧바로 내보내지 않고 메모리에 모아 두었다가 한 덩어리로 내보냅니다. 이것을 버퍼링이라고 합니다. 모아 두는 단위에 따라 셋으로 나뉩니다.
| 방식 | 내보내는 때 |
|---|---|
| 버퍼링 없음 | 되도록 곧바로 |
| 줄 버퍼링 | 줄바꿈을 만날 때 |
| 완전 버퍼링 | 모아 두는 공간이 찰 때 |
C 표준 라이브러리의 표준 출력은 1번이 터미널 같은 대화형 장치를 가리키지 않는다고 판단될 때, 그리고 그때만 완전 버퍼링이 됩니다. 1번을 파일로 돌리면 그 조건에 걸립니다. 표준 오류는 완전 버퍼링이 되지 않습니다.
파이썬도 비슷하게 움직입니다. 대화형이면 표준 출력이 줄 버퍼링입니다. 아니면 일반 파일처럼 블록 단위로 모읍니다. 표준 오류는 파이썬 3.9 부터 어느 경우든 줄 버퍼링입니다. -u 옵션이나 PYTHONUNBUFFERED 환경 변수를 주면 표준 출력과 표준 오류 둘 다 버퍼링을 끕니다.
그래서 서버의 로그를 파일로 돌려 두면 그 파일이 한동안 비어 보일 수 있습니다.
프로그램이 끝나는 길에 따라 남은 버퍼의 운명도 갈립니다. exit · _exit · abort 는 프로그램이 스스로 끝날 때 부르는 호출들입니다.
exit 로 끝나면 열린 스트림에 남은 버퍼를 모두 내보냅니다. _exit 로 끝나면 내보내지 않습니다. abort 처럼 다른 길로 끝날 때는 파일이 제대로 닫히지 않아도 됩니다. 그때 버퍼에 남았던 줄은 파일에 닿지 못할 수 있습니다.
flowchart TD
W["프로그램이 1번에 쓴다"] --> B["버퍼에 쌓인다"]
B -->|"버퍼가 찬다"| F["파일로 내보낸다"]
B --> E{"끝나는 길"}
E -->|"exit"| E1["남은 버퍼를 내보낸다"]
E -->|"_exit"| E2["내보내지 않는다"]
E -->|"abort"| E3["파일이 제대로 닫히지 않아도 된다"]
HTTP 리다이렉션 고리
리다이렉션이 서로를 가리키면 주소들이 고리를 이룹니다. A 가 B 로 보냅니다. B 는 다시 A 로 보냅니다. 이 고리를 알아채고 끼어드는 일은 클라이언트가 맡습니다(SHOULD).
클라이언트가 그렇게 하지 않으면 같은 주소들 사이를 끝없이 오가게 됩니다. 예전 판 HTTP 규격은 리다이렉션을 다섯 번까지로 권했습니다. 그래서 그런 고정 상한을 둔 클라이언트가 있을 수 있습니다.
POST 가 GET 으로 바뀌는 경우
POST 로 보낸 요청이 301 이나 302 를 받으면 클라이언트는 다음 요청을 GET 으로 바꿔 보내도 됩니다(MAY). 서버는 새 주소에서 POST 를 기다립니다. 그런데 받는 것은 GET 입니다.
까닭은 역사에 있습니다. HTTP/1.0 은 301 과 302 가 요청 방식을 바꾸지 않는다고 정했습니다. 초기 클라이언트들은 POST 를 POST 로 다시 보내는 클라이언트와 GET 으로 바꾸는 클라이언트로 갈렸습니다. 흔한 관행은 결국 GET 으로 바꾸는 것으로 모였습니다. 그래서 지금 규격은 301 과 302 에서 POST 를 GET 으로 바꾸는 것을 허용합니다.
방식이 바뀌면 안 되는 요청에는 307 이나 308 을 쓸 수 있습니다. 두 코드는 요청 방식을 지키는 리다이렉션을 뚜렷이 가리키려고 나중에 더해졌습니다.
안전한 메서드는 서버의 상태를 바꾸지 않는다고 정해진 요청 방식입니다. 안전하다고 알려지지 않은 요청 방식에서는 클라이언트가 자동으로 따라가는 일 자체를 조심해야 합니다. 사용자가 그 요청이 다른 곳으로 다시 가길 원하지 않을 수 있습니다.
관련 항목
셸 리다이렉션이 대상을 바꾸는 입출력 번호
파일 디스크립터 · 표준 입력 · 표준 출력 · 표준 오류 · 표준 입출력 · 터미널 · isatty
셸 리다이렉션을 정의하는 표준과 셸
셸 · POSIX · bash · KornShell · 특수 내장 명령 · 서브셸
셸 리다이렉션을 꾸미는 시스템 콜
시스템 콜 · 프로세스 · 커널 · fork · exec · open · dup2 · close · close-on-exec · posix_spawn
셸 리다이렉션과 나란히 입출력을 잇는 수단
파이프 · 파이프라인 · 히어 문서 · 명령 치환 · 프로세스 치환 · 네임드 파이프 · tee
셸 리다이렉션의 목적지가 되는 파일
널 장치 · 장치 파일 · 로그 파일 · 임시 파일 · 심볼릭 링크
셸 리다이렉션과 함께 쓰는 명령
ls · sort · grep · wc · cron · du
셸 리다이렉션에서 자주 나는 오류
noclobber · O_TRUNC · sudo · 파일 권한 · 버퍼링 · 표준 라이브러리
HTTP 리다이렉션을 이루는 응답과 헤더
리다이렉트 · 상태 코드 · 상태 코드 등급 · Location 헤더 · 300 Multiple Choices · 301 Moved Permanently · 302 Found · 303 See Other · 304 Not Modified · 307 Temporary Redirect · 308 Permanent Redirect
HTTP 리다이렉션이 오가는 프로토콜과 주소
HTTP · HTTP 응답 · RFC 9110 · URI · URL · 상대 경로 · 브라우저 · 사용자 에이전트 · HTTP 캐시 · HSTS
HTTP 리다이렉션에서 요청 방식이 갈리는 메서드
GET · POST · HEAD · 안전한 메서드 · 멱등성
HTTP 리다이렉션에서 자주 나는 오류와 공격
리다이렉트 루프 · 오픈 리다이렉트 · 피싱
HTTP 리다이렉션을 쓰는 인증 흐름
OAuth 2.0 · 리다이렉션 URI · 리다이렉션 엔드포인트 · 인가 서버 · 인가 코드 · OpenID Connect
다른 이름: redirection · 리디렉션 · 입출력 리다이렉션 · 입출력 재지정 · HTTP 리다이렉션 · URL 리다이렉션