Linuxコマンドのパイプを実装しながら学ぶ

Linuxコマンドのパイプというのは、

コマンド1 | コマンド2

のように2つ以上のコマンドを | で繋ぎ、コマンド1の結果をコマンド2に渡して処理を続けます。


ファイルの中の特定の文字列(パターン)がある行を抽出するコマンドのgrepと、ファイルの中のテキストを並べ替えして出力するsortを使いながら、パイプの処理を見ていきましょう。




話を始める前に、下記のコマンドでファイルのサンプルを作成しておきます。

$ micro fruits.txt
orange
apple
grape
pear
peach
apricot
chestnut
almond
lemon
citron

果物名の英語表記を改行区切りで並べたファイルを用意します。




最初にgrepの挙動を確認しておきます。

$ grep apple fruits.txt

grepは

grep 文字列(パターン) ファイルパス

で使用するコマンドで、今回はfruits.txtにappleという文字列があるか?という処理をしており、出力結果は

apple

になりました。


詳細は端折りますが、

$ grep '^a' fruits.txt

でfruits.txtからaから始まる文字列すべてを探すコマンドになりまして、出力結果は

apple
apricot
almond

になります。

※^aの詳細に触れたい時は正規表現で検索してください。




続いて、sortについて見ていきます。

$ sort fruits.txt
sort ファイルパス

でファイルの中で改行区切りで配置した文字列をアルファベット順に並び替えます。


出力内容は

almond
apple
apricot
chestnut
citron
grape
lemon
orange
peach
pear

になります。




grepとsortをパイプで繋いでみます。


$ grep '^a' fruits.txt | sort
almond
apple
apricot

grepで得られた値をアルファベット順に並び替えられた状態で出力されました。

パイプを介し、grepで得られた結果をsortに渡して処理を行った事になります。




今回はnarabikaeというコマンドを作成しながら、パイプについて見ていくことにします。

narabikaeコマンドはホームディレクトリ(今回であれば、/home/pi/以下)で作成します。


/home/pi/narabikae.goファイルを作成して、

package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
)

func main() {
	s := bufio.NewScanner(os.Stdin)

	var texts []string
	for s.Scan() {
		texts = append(texts, s.Text())
	}

	if len(texts) > 0 {
		// クイックソート
		sort.Slice(texts, func(i, j int) bool {
			return texts[i] < texts[j]
		})

		for _, text := range texts {
			fmt.Println(text)
		}
	}
}

のコードを保存します。


今回はパスを通さず、そのまま実行します。

$ go build narabikae.go
$ grep '^a' fruits.txt | ./narabikae 
almond
apple
apricot

grepで得られた値をソートした状態で出力されました。


Goで書いたコードで、grepからパイプを介して送られてきた値は

s := bufio.NewScanner(os.Stdin)

var texts []string
for s.Scan() {
	texts = append(texts, s.Text())
}

の箇所で一行ずつtexts配列に挿入しています。




今回のnarabikaeコマンドをsortと同じようにするためには、

narabikae ファイルパス

の命令も実行できるようにする必要がありますが、今回は触れない事にします。

マインクラフト用ビジュアルエディタを開発しています。
詳しくはinunosinsi/mcws_blockly - githubをご覧ください。