いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

シェルでパイプ繋いで実行したときプログラムは同時に起動されている (bash)

概要

タイトルの通りですが、これ間違えて認識している人が結構周りにも多いので、ついでにメモメモ。

私も昔は間違えた認識してましたw

コマンドをパイプで繋いで実行することはよくあると思います。

$ grep "pattern" /path/to/file | sort | head

こんな感じとかですね。

このとき、私は昔、以下のように理解していました。

最初のコマンドが起動して処理して、出てきた出力を次のプログラムが起動して受け取って処理して・・・

パイプを境に左から 順に プログラムが起動していって出力が流れていくんだーって感じで理解。

んで、いつのことか忘れてしまいましたが、nc(1) の man を見ていたときに以下の記述を見つけました。

$ rm -f /tmp/f; mkfifo /tmp/f

$ cat /tmp/f | /bin/sh -i 2>&1 | nc -l 127.0.0.1 1234 > /tmp/f

1行目はまあ普通。mkfifo で名前付きパイプを作っているだけですね。

2行目が当時のワタシには意味が理解できませんでした。

一番左のコマンドである cat で read してて、一番右の nc で write してる。名前付きパイプは誰かが write しないと read できないし、誰かが read しないと write できないものです。

「左のコマンドが起動した時点で右の write 側はまだ起動していないから 何も読み取れないじゃん・・・」って思ってしまいました。

でも、上のコマンドを実行すると普通に動きます。何故だ??・・・ってなっていました当時w

プログラムは同時に起動される

間違えて理解してしまっていると、固定観念みたいになってしまっていてなかなか別の発想にならないんですよね。こういうときw

何のことはない。パイプで実行しているとき、それぞれのプログラムは 同時に起動 されています。

なので、前述したコマンドもちゃんと動くということでした。

サンプル

書くほどのことでも無いのですが、何事も自分でプログラム書いてみて確認するのが一番。

てことで、ちょっとしたサンプルつくってみましょう。

5秒おきに標準エラー出力になにかを出力しながら、標準入力を受け取ったら標準出力になにかを出力するプログラムです。

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
    "os/signal"
    "time"
)

func main() {
    var (
        sigCh = make(chan os.Signal, 1)
    )

    signal.Notify(sigCh, os.Interrupt)

    var (
        lout = log.New(os.Stdout, fmt.Sprintf("[STDOUT][%s]: ", os.Args[0]), 0)
        lerr = log.New(os.Stderr, fmt.Sprintf("[STDERR][%s]: ", os.Args[0]), 0)
    )

    go func() {
        for {
            select {
            case <-time.After(5 * time.Second):
                lerr.Println("helloworld")
            }
        }
    }()

    go func() {
        s := bufio.NewScanner(os.Stdin)
        for s.Scan() {
            lout.Println(s.Text())
        }
    }()

    <-sigCh
    lerr.Println("done")
}

ビルドと実行用に Makefile

default: all

all: clean build run

clean:
  rm -f ./p1 ./p2

build:
  go build -race -o p1 main.go
  go build -race -o p2 main.go

run:
  ./p1 | ./p2

実行すると以下のようになります。

gitpod /workspace/gitpod-playground $ make
rm -f ./p1 ./p2
go build -race -o p1 main.go
go build -race -o p2 main.go
./p1 | ./p2

[STDERR][./p2]: helloworld
[STDERR][./p1]: helloworld
[STDERR][./p2]: helloworld
[STDERR][./p1]: helloworld
aaaaa
[STDOUT][./p2]: [STDOUT][./p1]: aaaaa
[STDERR][./p2]: helloworld
[STDERR][./p1]: helloworld
^C[STDERR][./p1]: done
[STDERR][./p2]: done
make: *** [Makefile:14: run] Interrupt

aaaaaって表示されている部分がキーボードから入力してエンターした部分です。

それまで何もしていないのに、2つのプログラムが標準エラー出力に出力しています。なので、両方起動している ということですね。


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com