grep
에서 stderr
는 필터링되지 않는다고 한다. 그럼 리다이렉션 시켜야 하는데 잠깐만... 표준 입출력이라면 각 프로그래밍 언어에서 stdin
, stdout
, stderr
는 어떻게 매칭될까? 에러는 정말 stderr
로 출력되고 있을까?
개요
cp -f
를 하는데 같은 파일이 이미 있다고 경고 메시지가 나왔다. 문제가 될 수 있지만 -f
로 강제화했기 때문에 큰 관심이 없었다. 하지만 이것이 계속 표시되니 실질적인 오류와 구별이 되지 않아 불편했다.
경고 메시지를 제외하기 위해 grep
으로 필터링하려 했지만 제대로 되지 않았다. 이유는 grep
은 표준 출력(stdout)
만 필터링하는데 cp
명령어의 경고 메시지는 표준 에러(stderr)
로 출력되기 때문이다. 이를 해결하기 위해선 stderr
를 stdout
으로 리다이렉션해야 한다고 한다.
stdin
, stdout
는 C언어
를 하며 보았던 익숙한 친구인데 이런 상황에서 만나니 어떤 역할을 하는지 까먹었다. 어떤 프로그램이든 바이너리로 컴파일되면 표준 스트림
형식으로 처리된다는 것인데, 각 언어마다 어떻게 구현되는지 궁금증이 생겼다. 또한, 이를 잘 활용하면 CLI
프로그램 만들 때 의미론적인 의도로 정보를 전달할 수 있어 보였다.
한번 알아보자.
리다이렉션(Redirection)
방법
먼저, 이 궁금증이 시작되었던 grep
에서 표준 에러(stderr)
를 필터링하기 위해선 표준 출력(stdout)
으로 리다이렉션해야 하는데, 리다이렉션의 주요 내용은 아래와 같다.
파일디스크립터(일련번호) | 이름 | 용도 |
---|---|---|
0 | 표준 입력(stdin) | 명령어에 입력될 내용을 저장 |
1 | 표준 출력(stdout) | 명령어에서 출력될 내용을 저장 |
2 | 표준 오류(stderr) | 명령어에서 출력될 에러 메시지를 저장 |
- Redirection 종류
<
>
(덮어쓰기)>>
(파일 끝에 추가)|
- 기타
>
는1>
의 축약된 형태- 어떤 사유로든 출력을 아예 무시하고 싶다면
/dev/null
로 지정
예제
출력 리다이렉션:
>
$ cat .gitignore > test.log
$ cat test.log
.idea
.DS_Store오류 리다이렉션:
2>
$ cat gitignore 2> test.log
$ cat test.log
cat: gitignore: No such file or directory출력과 오류 모두 출력:
&
$ cat .gitignore &>> test.log
출력 무시
# 성공 출력
$ cat gitignore > /dev/null
# 오류 출력
$ cat gitignore 2> /dev/null출력과 오류 분리하여 로그 저장
$ {명령어} > success.log 2> error.log
오류 출력을 표준 출력으로 결합:
2>&1
$ {명령어} > any.log 2>&1
주의점
- 왜
2>&1
에서&
기호를 붙이는가&
를 안 쓰면 파일로 읽히기 때문에 구분 값으로 사용됨
- 파일디스크립터 지정할 때
>
와 붙여야 한다.2 > /dev/null
안 되고2> /dev/null
해야 함
파이프 처리를 쪼개보면
리다이렉션(Redirection)을 이해하고 파이프 처리를 쪼개보면 재미있다.
명령어1 | 명령어2
는 아래와 같다고 볼 수 있다.
$ 명령어1 > 임시파일
$ 명령어2 < 임시파일
$ rm 임시파일
프로그래밍 언어에서 stdout
, stderr
구분하여 출력 방법
표준 스트림이라면 프로그래밍 언어에서는 어떻게 구현되어 있을까?
Node.js
// 표준 출력에 메시지 출력
console.log("이것은 표준 출력 메시지입니다.");
// 표준 에러에 메시지 출력
console.error("이것은 표준 에러 메시지입니다.");Go
package main
import (
"fmt"
"os"
)
func main() {
// 표준 출력에 메시지 출력
fmt.Println("이것은 표준 출력 메시지입니다.")
// 표준 에러에 메시지 출력
_, err := fmt.Fprintln(os.Stderr, "이것은 표준 에러 메시지입니다.")
if err != nil {
// 에러 처리
panic(err)
}
}C#
(실제로 확인 안 해봄)using System;
class Program
{
static void Main()
{
// 표준 출력에 메시지 출력
Console.WriteLine("이것은 표준 출력 메시지입니다.");
// 표준 에러에 메시지 출력
Console.Error.WriteLine("이것은 표준 에러 메시지입니다.");
}
}PHP
(실제로 확인 안 해봄)<?php
// 표준 출력에 메시지 출력
echo "이것은 표준 출력 메시지입니다.\n";
// 표준 에러에 메시지 출력
fwrite(STDERR, "이것은 표준 에러 메시지입니다.\n");
?>C++
(실제로 확인 안 해봄)#include <iostream>
int main() {
// 표준 출력에 메시지 출력
std::cout << "이것은 표준 출력 메시지입니다." << std::endl;
// 표준 에러에 메시지 출력
std::cerr << "이것은 표준 에러 메시지입니다." << std::endl;
return 0;
}
마치면서
그동안 추상적으로 알던 stdin
, stdout
, stderr
를 좀 더 명확히 알 수 있었다. 어찌 보면 이걸 이제서야 제대로 알게 된 것도 부끄럽다.
터미널의 일회성 명령어를 사용할 때는 정상 출력, 오류 출력이 함께 표시되어도 별 문제가 없지만, 스크립트를 실행하거나 데몬과 같이 지속적으로 수행되는 프로그램일 때는 표준 스트림을 목적에 맞게 적절히 사용해야 할 것이다.
이 내용을 바탕으로 프로그램 만들 때 더 신중하게 출력 방식을 선택할 수 있게 되었다.
Reference
- Hub
- 친절한 설명과
Linux
의 입출력 기본적인 내용에 대해서 알 수 있습니다. - 위키백과: 리다이렉션 설명 리다이렉션