パイプとフィルタ

最終更新日:2024-11-22 | ページの編集

概要

質問

  • 既存のコマンドを組み合わせて、目的の出力を得るにはどうすればよいですか?
  • 出力の一部だけを表示するにはどうすればよいですか?

目的

  • パイプとフィルタを使用してコマンドをリンクする利点を説明する。
  • コマンドのシーケンスを組み合わせて新しい出力を得る。
  • コマンドの出力をファイルにリダイレクトする。
  • プログラムやパイプラインが処理する入力を与えられない場合に通常何が起こるかを説明する。

いくつかの基本的なコマンドを学んだので、シェルの最も強力な機能に目を向けましょう: 既存のプログラムを新しい方法で簡単に組み合わせることができる点です。 shell-lesson-data/exercise-data/alkanesディレクトリを使用して、 いくつかの簡単な有機分子を記述した6つのファイルを調べます。 .pdb拡張子は、これらのファイルがProtein Data Bank形式であることを示しています。 これは、分子内の各原子の種類と位置を指定するシンプルなテキスト形式です。

BASH

$ ls

出力

cubane.pdb    methane.pdb    pentane.pdb
ethane.pdb    octane.pdb     propane.pdb

例として、次のコマンドを実行してみましょう:

BASH

$ wc cubane.pdb

出力

20  156 1158 cubane.pdb

wcは「ワードカウント」コマンドです: ファイル内の行数、単語数、および文字数をカウントします(この順に左から右に値が表示されます)。

コマンドwc *.pdbを実行すると、*は0文字以上に一致するため、 シェルは*.pdbを現在のディレクトリ内のすべての.pdbファイルのリストに展開します:

BASH

$ wc *.pdb

出力

  20  156  1158  cubane.pdb
  12   84   622  ethane.pdb
   9   57   422  methane.pdb
  30  246  1828  octane.pdb
  21  165  1226  pentane.pdb
  15  111   825  propane.pdb
 107  819  6081  total

wc *.pdbの最後の行は、すべての行の合計を示しています。

wcではなくwc -lを実行すると、出力には各ファイルの行数のみが表示されます:

BASH

$ wc -l *.pdb

出力

  20  cubane.pdb
  12  ethane.pdb
   9  methane.pdb
  30  octane.pdb
  21  pentane.pdb
  15  propane.pdb
 107  total

また、wcコマンドでは、-mオプションで文字数のみ、-wオプションで単語数のみを表示できます。

何も起こらない理由は?

コマンドがファイルを処理することを想定しているにもかかわらず、 ファイル名を指定しない場合はどうなりますか? たとえば、次のように入力した場合:

BASH

$ wc -l

しかし、*.pdb(またはその他のファイル名)をコマンドの後に指定しなかった場合、 wcはコマンドプロンプトで与えられる入力を処理する必要があると仮定します。 そのため、データを手動で入力するまで待機します。 外部から見ると、何も起こっていないように見えます。

このようなミスをした場合は、Ctrlキーを押しながらCキーを押すことで 状態を脱出できます:Ctrl+Cを押した後に両方のキーを離してください。

コマンドの出力をキャプチャする


どのファイルが最も行数が少ないでしょうか? ファイルが6つしかない場合は簡単な質問ですが、 6000個あった場合はどうでしょう? 解決の第一歩は、次のコマンドを実行することです:

BASH

$ wc -l *.pdb > lengths.txt

大なり記号>は、コマンドの出力を画面ではなくファイルに リダイレクトするようにシェルに指示します。 このコマンドは画面に何も出力しません。 なぜなら、wcが出力するはずだったすべての内容が lengths.txtファイルに保存されるためです。 このコマンドを発行する前にファイルが存在しない場合、 シェルはそのファイルを作成します。 すでに存在する場合、ファイルは黙って上書きされるため、データ損失につながる可能性があります。 したがって、リダイレクトコマンドを使用する際には注意が必要です。

ls lengths.txtコマンドでファイルが存在することを確認できます:

BASH

$ ls lengths.txt

出力

lengths.txt

lengths.txtの内容を画面に表示するには、 cat lengths.txtコマンドを使用します。 catコマンドは「連結」を意味し、 ファイルの内容を順に表示します。 この場合、1つのファイルしかないため、 catはその内容をそのまま表示します:

BASH

$ cat lengths.txt

出力

  20  cubane.pdb
  12  ethane.pdb
   9  methane.pdb
  30  octane.pdb
  21  pentane.pdb
  15  propane.pdb
 107  total

出力をページ単位で表示

このレッスンでは利便性と一貫性のためにcatを使用しますが、 画面全体にファイルを一気に表示するという欠点があります。 実際にはlessコマンド(例:less lengths.txt)の方が便利です。 これはファイルの1画面分を表示して停止します。 スペースバーで1画面分進み、 bで1画面分戻ることができます。終了するにはqを押してください。

出力のフィルタリング


次に、sortコマンドを使用してlengths.txtファイルの内容をソートします。 その前に、sortコマンドについて少し学ぶための練習問題を行います:

sort -nは何をする?

ファイルshell-lesson-data/exercise-data/numbers.txtには以下の行が含まれています:

10
2
19
22
6

このファイルでsortを実行すると、出力は次のようになります:

出力

10
19
2
22
6

同じファイルでsort -nを実行すると、次の出力が得られます:

出力

2
6
10
19
22

なぜ-nがこのような効果を持つのか説明してください。

-nオプションは、アルファベット順ではなく数値順のソートを指定します。

-nオプションを使用すると、ソートがアルファベット順ではなく数値順になることを指定します。 この操作はファイル自体を変更するわけではなく、ソートされた結果を画面に送ります:

BASH

$ sort -n lengths.txt

出力

  9  methane.pdb
 12  ethane.pdb
 15  propane.pdb
 20  cubane.pdb
 21  pentane.pdb
 30  octane.pdb
107  total

> sorted-lengths.txtをコマンドの後に付け加えることで、 ソートされた行のリストをsorted-lengths.txtという一時ファイルに保存できます。 その後、headコマンドを使用してsorted-lengths.txtの最初の数行を取得します:

BASH

$ sort -n lengths.txt > sorted-lengths.txt
$ head -n 1 sorted-lengths.txt

出力

  9  methane.pdb

-n 1オプションをheadに指定すると、ファイルの最初の1行だけが出力されます。 -n 20を指定すると最初の20行が得られる、というように使えます。 sorted-lengths.txtにはファイルの行数が少ない順に並んでいるため、 headの出力は行数が最も少ないファイルとなります。

同じファイルへのリダイレクト

コマンドの出力をそのコマンドが操作しているファイルに リダイレクトするのは非常に危険です。たとえば:

BASH

$ sort -n lengths.txt > lengths.txt

このような操作を行うと、不正な結果が得られるか、 lengths.txtの内容が削除される可能性があります。

>>の意味は?

>の使用法を学びましたが、>>という似たオペレーターもあります。 これは少し異なる動作をします。 以下のコマンドを実行して、この2つのオペレーターの違いを明らかにしてください:

BASH

$ echo The echo command prints text

出力

The echo command prints text

次のコマンドをテストしてください:

BASH

$ echo hello > testfile01.txt

と:

BASH

$ echo hello >> testfile02.txt

ヒント:各コマンドを2回連続で実行し、その後出力ファイルを調べてください。

最初の例(>を使用)では、文字列hellotestfile01.txtに書き込まれますが、 コマンドを再度実行するとファイルが上書きされます。

2番目の例(>>を使用)では、helloがファイルに書き込まれます (この場合はtestfile02.txt)が、すでにファイルが存在する場合は文字列が追加されます。 つまり、2回目の実行時には追記されます。

データの追記

すでにheadコマンドを学びました。このコマンドはファイルの先頭から行を出力します。 tailはこれに似ていますが、ファイルの末尾から行を出力します。

以下のコマンドの後、animals-subset.csvファイルに対応する答えを選択してください:

BASH

$ head -n 3 animals.csv > animals-subset.csv
$ tail -n 2 animals.csv >> animals-subset.csv
  1. animals.csvの最初の3行
  2. animals.csvの最後の2行
  3. animals.csvの最初の3行と最後の2行
  4. animals.csvの2行目と3行目

正解は選択肢3です。 選択肢1が正しい場合は、headコマンドのみを実行します。 選択肢2が正しい場合は、tailコマンドのみを実行します。 選択肢4が正しい場合は、次のようにheadの出力をtailにパイプで渡す必要があります: head -n 3 animals.csv | tail -n 2 > animals-subset.csv

出力を別のコマンドに渡す


行数が最も少ないファイルを見つける例では、 出力を保存するためにlengths.txtsorted-lengths.txtという 2つの中間ファイルを使用しています。 しかし、これでは操作がわかりにくくなります。 wcsortheadが何をするのか理解していても、 中間ファイルの存在が混乱を招きます。 これを簡単に理解できるように、sortheadを連続して実行します:

BASH

$ sort -n lengths.txt | head -n 1

出力

  9  methane.pdb

垂直バー|パイプと呼ばれます。 これは、左側のコマンドの出力を右側のコマンドの入力として使用することをシェルに指示します。

これにより、sorted-lengths.txtファイルは不要になりました。

複数のコマンドを組み合わせる


パイプを連続してチェーンすることも可能です。 たとえば、wcの出力を直接sortに送り、 さらにその結果をheadに送ることができます。 これにより、中間ファイルは一切不要になります。

まず、パイプを使用してwcの出力をsortに送ります:

BASH

$ wc -l *.pdb | sort -n

出力

   9 methane.pdb
  12 ethane.pdb
  15 propane.pdb
  20 cubane.pdb
  21 pentane.pdb
  30 octane.pdb
 107 total

次に、その出力をheadに送ることで、完全なパイプラインは次のようになります:

BASH

$ wc -l *.pdb | sort -n | head -n 1

出力

   9  methane.pdb

これは、数学者がlog(3x)のように関数をネストして、 「3xの対数」と説明するのと同じです。 私たちの場合、アルゴリズムは 「*.pdbの行数をソートして先頭を取得する」というものです。

上記のコマンドで使用されたリダイレクトとパイプの概念を以下に示します:

リダイレクトとパイプの例。"wc -l *.pdb"はシェルに出力を送る。"wc -l *.pdb > lengths"は出力をファイル"lengths"に送る。"wc -l *.pdb | sort -n | head -n 1"はパイプラインを構築し、"wc"コマンドの出力を"sort"コマンドの入力とし、"sort"コマンドの出力を"head"コマンドの入力とし、最終的に"head"コマンドの出力をシェルに送る

コマンドをパイプでつなぐ

現在のディレクトリ内で、行数が最も少ない3つのファイルを見つけたいとします。 以下のどのコマンドが機能するでしょうか?

  1. wc -l * > sort -n > head -n 3
  2. wc -l * | sort -n | head -n 1-3
  3. wc -l * | head -n 3 | sort -n
  4. wc -l * | sort -n | head -n 3

正解は選択肢4です。 パイプ文字|は、あるコマンドの出力を 別のコマンドの入力として接続するために使用されます。 >は標準出力をファイルにリダイレクトします。 shell-lesson-data/exercise-data/alkanesディレクトリで試してみてください!

協力するために設計されたツール


このプログラムをつなげて使うというアイデアこそが、Unixが成功した理由です。 多くの異なるタスクを実行する巨大なプログラムを作成する代わりに、 Unixのプログラマーは、各ツールが1つの仕事をうまくこなし、 お互いにうまく連携できるようにすることに集中します。 このプログラミングモデルは「パイプとフィルタ」と呼ばれます。 私たちはすでにパイプを見てきました。 フィルタとは、wcsortのように、 入力ストリームを出力ストリームに変換するプログラムのことです。 標準的なUnixツールのほぼすべてがこのように動作します。 特別な指示がない限り、 これらは標準入力から読み取り、 読み取った内容を処理し、 標準出力に書き出します。

標準入力からテキストの行を読み取り、 標準出力にテキストの行を書き出すプログラムは、 同じように振る舞う他のすべてのプログラムと組み合わせることができます。 プログラムをこのように作成することで、 自分自身や他の人々がそれらをパイプラインに組み込んで その能力を何倍にも拡張できるようにするべきです。

パイプを読む

ファイルanimals.csvshell-lesson-data/exercise-data/animal-countsフォルダ内)には以下のデータが含まれています:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-06,fox,4
2012-11-07,rabbit,16
2012-11-07,bear,1

以下のパイプラインと最終リダイレクトを通過するテキストは何ですか? 注:sort -rコマンドは逆順にソートします。

BASH

$ cat animals.csv | head -n 5 | tail -n 3 | sort -r > final.txt

ヒント:理解をテストするために、1つずつコマンドを積み上げてパイプラインを構築してみてください。

headコマンドはanimals.csvの最初の5行を抽出します。 次に、tailコマンドを使用して、最初の5行から最後の3行を抽出します。 sort -rコマンドを使用して、それらの3行を逆順にソートします。 最後に、出力はfinal.txtというファイルにリダイレクトされます。 このファイルの内容は、cat final.txtを実行することで確認できます。 ファイルには次の行が含まれるはずです:

2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-05,raccoon,7

パイプの構築

前の演習のファイルanimals.csvを使用して、次のコマンドを考えます:

BASH

$ cut -d , -f 2 animals.csv

cutコマンドは、ファイル内の各行から特定の部分を削除または「切り取る」ために使用されます。 cutは、行がTab文字で区切られていることを前提としています。 このように使用される文字は区切り文字と呼ばれます。 上記の例では、-dオプションを使用してカンマを区切り文字として指定しています。 また、-fオプションを使用して、2番目のフィールド(列)を抽出することを指定しています。 これにより、次の出力が得られます:

出力

deer
rabbit
raccoon
rabbit
deer
fox
rabbit
bear

uniqコマンドは、ファイル内の隣接する一致する行をフィルタリングします。 このパイプラインをどのように拡張して、 ファイルに含まれる動物の名前を重複なしで見つけることができますか?

BASH

$ cut -d , -f 2 animals.csv | sort | uniq

どのパイプを使用する?

ファイルanimals.csvには以下のようにフォーマットされた8行のデータが含まれています:

出力

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
...

uniqコマンドには、入力内で行が出現した回数をカウントする-cオプションがあります。 現在のディレクトリがshell-lesson-data/exercise-data/animal-countsであると仮定して、 ファイル内の各動物の合計数を示すテーブルを生成するには、 どのコマンドを使用しますか?

  1. sort animals.csv | uniq -c
  2. sort -t, -k2,2 animals.csv | uniq -c
  3. cut -d, -f 2 animals.csv | uniq -c
  4. cut -d, -f 2 animals.csv | sort | uniq -c
  5. cut -d, -f 2 animals.csv | sort | uniq -c | wc -l

正解は選択肢4です。 なぜそうなるのか理解が難しい場合は、 コマンドやパイプラインのサブセクションを実行してみてください (shell-lesson-data/exercise-data/animal-countsディレクトリにいることを確認してください)。

ネルのパイプライン: ファイルの確認


ネルはサンプルをアッセイマシンに通し、先に説明した north-pacific-gyre ディレクトリに17個のファイルを作成しました。
簡単にチェックするために、shell-lesson-data ディレクトリから次のコマンドを入力します:

BASH

$ cd north-pacific-gyre
$ wc -l *.txt

出力は以下のように18行が表示されます:

出力

300 NENE01729A.txt
300 NENE01729B.txt
300 NENE01736A.txt
300 NENE01751A.txt
300 NENE01751B.txt
300 NENE01812A.txt
... ...

次に以下を入力します:

BASH

$ wc -l *.txt | sort -n | head -n 5

出力

 240 NENE02018B.txt
 300 NENE01729A.txt
 300 NENE01729B.txt
 300 NENE01736A.txt
 300 NENE01751A.txt

おっと、1つのファイルが他より60行少ないことに気づきました。
ログを確認すると、そのアッセイは月曜日の朝8時に実行されており、週末に誰かが機械を使用し、リセットするのを忘れた可能性があります。
そのサンプルを再実行する前に、データが多すぎるファイルがないか確認します:

BASH

$ wc -l *.txt | sort -n | tail -n 5

出力

 300 NENE02040B.txt
 300 NENE02040Z.txt
 300 NENE02043A.txt
 300 NENE02043B.txt
5040 total

これらの数値は問題なさそうですが、3行目の「Z」は何でしょうか?
彼女のサンプルはすべて「A」または「B」でマークされているはずです。
慣例として、彼女の研究室では「Z」を欠損情報があるサンプルを示すために使用します。
これに似た他のサンプルを探すために、次のコマンドを実行します:

BASH

$ ls *Z.txt

出力

NENE01971Z.txt    NENE02040Z.txt

やはり、彼女がラップトップのログを確認すると、これらのサンプルの深度が記録されていません。
他の方法で情報を取得するには遅すぎるため、これら2つのファイルを解析から除外する必要があります。
rm を使用して削除することもできますが、深度が重要でない解析を行う可能性もあるため、代わりに後でワイルドカード式 NENE*A.txt NENE*B.txt を使ってファイルを選択するよう注意する必要があります。

不要なファイルの削除

ストレージを節約するために、処理済みデータファイルを削除し、生データファイルと処理スクリプトのみを保持したいとします。
生データファイルは .dat で終わり、処理済みファイルは .txt で終わります。
次のうち、処理済みデータファイルのみを削除するコマンドはどれでしょうか?

  1. rm ?.txt
  2. rm *.txt
  3. rm * .txt
  4. rm *.*
  1. このコマンドは1文字の名前を持つ .txt ファイルを削除します。
  2. これが正しい答えです。
  3. シェルは * を現在のディレクトリ内のすべてのファイルに展開するため、このコマンドはすべての一致するファイルと追加の .txt という名前のファイルを削除しようとします。
  4. シェルは *.* を拡張して少なくとも1つの . を含むすべてのファイル名に一致させるため、処理済みファイル (.txt) と生データファイル (.dat) の両方が削除されます。

まとめ

  • wcは入力内の行、単語、および文字数をカウントします。
  • catは入力の内容を表示します。
  • sortは入力をソートします。
  • headはデフォルトで入力の最初の10行を表示します。
  • tailはデフォルトで入力の最後の10行を表示します。
  • command > [file]はコマンドの出力をファイルにリダイレクトします(既存の内容を上書きします)。
  • command >> [file]はコマンドの出力をファイルに追記します。
  • [first] | [second]はパイプラインです:最初のコマンドの出力が2番目のコマンドの入力として使用されます。
  • シェルを使用する最良の方法は、単純で一つの目的を持つプログラム(フィルタ)をパイプで組み合わせることです。