본문으로 건너뛰기

표준 스트림: stdout만 보는 grep, stderr는 어떻게?

 · reading-time-plural

grep 에서 stderr 는 필터링되지 않는다고 한다. 그럼 리다이렉션 시켜야 하는데 잠깐만... 표준 입출력이라면 각 프로그래밍 언어에서 stdin , stdout , stderr 는 어떻게 매칭될까? 에러는 정말 stderr 로 출력되고 있을까?

개요

cp -f 를 하는데 같은 파일이 이미 있다고 경고 메시지가 나왔다. 문제가 될 수 있지만 -f 로 강제화했기 때문에 큰 관심이 없었다. 하지만 이것이 계속 표시되니 실질적인 오류와 구별이 되지 않아 불편했다.

경고 메시지를 제외하기 위해 grep 으로 필터링하려 했지만 제대로 되지 않았다. 이유는 grep표준 출력(stdout) 만 필터링하는데 cp 명령어의 경고 메시지는 표준 에러(stderr) 로 출력되기 때문이다. 이를 해결하기 위해선 stderrstdout 으로 리다이렉션해야 한다고 한다.

stdin , stdoutC언어 를 하며 보았던 익숙한 친구인데 이런 상황에서 만나니 어떤 역할을 하는지 까먹었다. 어떤 프로그램이든 바이너리로 컴파일되면 표준 스트림 형식으로 처리된다는 것인데, 각 언어마다 어떻게 구현되는지 궁금증이 생겼다. 또한, 이를 잘 활용하면 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


parkgang
태그 🏷