파이프

발행: (2025년 12월 31일 오전 04:57 GMT+9)
4 min read
원문: Dev.to

Source: Dev.to

Pipes에 대한 표지 이미지

The problem

Go에서 다음과 같은 셸 명령과 동일하게 동작하는 파이프라인을 실행하고 싶었습니다:

tail -f test.log | head -3

tail -f는 스스로 종료되지 않으므로, 첫 번째 명령이 계속 실행되는 동안 두 번째(head)가 필요한 줄 수를 읽고 종료되는 상황을 파이프라인이 처리해야 합니다.

Demonstration in the shell

# In one terminal
echo line1 > test.log
tail -f ./test.log | head -3

다른 터미널에서:

echo line2 >> ./test.log

head는 줄이 나타나는 즉시 출력합니다. 세 번째 줄이 기록되면 head는 세 번째 줄을 받고 종료합니다. 네 번째 쓰기에서는 파이프의 읽기 끝이 닫혔기 때문에 tailSIGPIPE를 받아 종료됩니다.

Bash 문서에서도 이 동작을 확인할 수 있습니다:

파이프라인의 각 명령 출력은 다음 명령 입력에 파이프를 통해 연결됩니다. 각 명령은 이전 명령의 출력을 읽습니다.
다중 명령 파이프라인의 각 명령은 자체 서브쉘에서 실행되며, 이는 별도의 프로세스입니다.
Bash Reference Manual – Pipelines

Mimicking the behavior in Go

Creating the commands

cmd1 := exec.Command("tail", "-f", "./test.log")
cmd2 := exec.Command("head", "-n", "5")

Connecting the pipeline

outPipe, err := cmd1.StdoutPipe()
if err != nil {
    // handle error
}
cmd2.Stdin = outPipe
cmd2.Stdout = os.Stdout // forward head's output to the Go program's stdout

Starting the processes

if err := cmd1.Start(); err != nil {
    // handle error
}
if err := cmd2.Start(); err != nil {
    // handle error
}

Waiting for completion

cmd2(즉 head 명령)가 먼저 끝납니다. head가 종료된 후 부모 프로세스에서 파이프의 읽기 끝을 닫아야 합니다; 그렇지 않으면 파이프가 열려 있게 되고 tailSIGPIPE를 받지 못합니다.

cmd2.Wait()          // wait for head to finish
outPipe.Close()      // close the pipe in the parent
cmd1.Wait()          // now tail can exit

Complete example

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    cmd1 := exec.Command("tail", "-f", "/Users/skuf_love/study/go/test_pipes/test.log")
    cmd2 := exec.Command("head", "-n", "5")

    outPipe, err := cmd1.StdoutPipe()
    if err != nil {
        fmt.Printf("StdoutPipe error: %v\n", err)
        return
    }

    cmd2.Stdin = outPipe
    cmd2.Stdout = os.Stdout

    if err = cmd1.Start(); err != nil {
        fmt.Printf("cmd1 start error: %v\n", err)
        return
    }
    if err = cmd2.Start(); err != nil {
        fmt.Printf("cmd2 start error: %v\n", err)
        return
    }

    // Wait for head to finish, then close the pipe so tail receives SIGPIPE
    cmd2.Wait()
    fmt.Println("cmd2 wait done")
    outPipe.Close()
    cmd1.Wait()
    fmt.Println("cmd1 wait done")
}

이 프로그램은 셸 파이프라인 동작을 재현합니다: headtest.log에 기록된 처음 다섯 줄을 읽고 종료하며, tailSIGPIPE를 받아 종료됩니다.

Back to Blog

관련 글

더 보기 »