パイプとフィルタ
最終更新日:2024-11-22 | ページの編集
所要時間: 35分
概要
質問
- 既存のコマンドを組み合わせて、目的の出力を得るにはどうすればよいですか?
- 出力の一部だけを表示するにはどうすればよいですか?
目的
- パイプとフィルタを使用してコマンドをリンクする利点を説明する。
- コマンドのシーケンスを組み合わせて新しい出力を得る。
- コマンドの出力をファイルにリダイレクトする。
- プログラムやパイプラインが処理する入力を与えられない場合に通常何が起こるかを説明する。
いくつかの基本的なコマンドを学んだので、シェルの最も強力な機能に目を向けましょう:
既存のプログラムを新しい方法で簡単に組み合わせることができる点です。
shell-lesson-data/exercise-data/alkanes
ディレクトリを使用して、
いくつかの簡単な有機分子を記述した6つのファイルを調べます。
.pdb
拡張子は、これらのファイルがProtein Data
Bank形式であることを示しています。
これは、分子内の各原子の種類と位置を指定するシンプルなテキスト形式です。
出力
cubane.pdb methane.pdb pentane.pdb
ethane.pdb octane.pdb propane.pdb
例として、次のコマンドを実行してみましょう:
出力
20 156 1158 cubane.pdb
wc
は「ワードカウント」コマンドです:
ファイル内の行数、単語数、および文字数をカウントします(この順に左から右に値が表示されます)。
コマンドwc *.pdb
を実行すると、*
は0文字以上に一致するため、
シェルは*.pdb
を現在のディレクトリ内のすべての.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
を実行すると、出力には各ファイルの行数のみが表示されます:
出力
20 cubane.pdb
12 ethane.pdb
9 methane.pdb
30 octane.pdb
21 pentane.pdb
15 propane.pdb
107 total
また、wc
コマンドでは、-m
オプションで文字数のみ、-w
オプションで単語数のみを表示できます。
何も起こらない理由は?
コマンドの出力をキャプチャする
どのファイルが最も行数が少ないでしょうか? ファイルが6つしかない場合は簡単な質問ですが、 6000個あった場合はどうでしょう? 解決の第一歩は、次のコマンドを実行することです:
大なり記号>
は、コマンドの出力を画面ではなくファイルに
リダイレクトするようにシェルに指示します。
このコマンドは画面に何も出力しません。
なぜなら、wc
が出力するはずだったすべての内容が
lengths.txt
ファイルに保存されるためです。
このコマンドを発行する前にファイルが存在しない場合、
シェルはそのファイルを作成します。
すでに存在する場合、ファイルは黙って上書きされるため、データ損失につながる可能性があります。
したがって、リダイレクトコマンドを使用する際には注意が必要です。
ls lengths.txt
コマンドでファイルが存在することを確認できます:
出力
lengths.txt
lengths.txt
の内容を画面に表示するには、
cat lengths.txt
コマンドを使用します。
cat
コマンドは「連結」を意味し、
ファイルの内容を順に表示します。 この場合、1つのファイルしかないため、
cat
はその内容をそのまま表示します:
出力
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
オプションを使用すると、ソートがアルファベット順ではなく数値順になることを指定します。
この操作はファイル自体を変更するわけではなく、ソートされた結果を画面に送ります:
出力
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
の最初の数行を取得します:
出力
9 methane.pdb
-n 1
オプションをhead
に指定すると、ファイルの最初の1行だけが出力されます。
-n 20
を指定すると最初の20行が得られる、というように使えます。
sorted-lengths.txt
にはファイルの行数が少ない順に並んでいるため、
head
の出力は行数が最も少ないファイルとなります。
最初の例(>
を使用)では、文字列hello
がtestfile01.txt
に書き込まれますが、
コマンドを再度実行するとファイルが上書きされます。
2番目の例(>>
を使用)では、hello
がファイルに書き込まれます
(この場合はtestfile02.txt
)が、すでにファイルが存在する場合は文字列が追加されます。
つまり、2回目の実行時には追記されます。
データの追記
すでにhead
コマンドを学びました。このコマンドはファイルの先頭から行を出力します。
tail
はこれに似ていますが、ファイルの末尾から行を出力します。
以下のコマンドの後、animals-subset.csv
ファイルに対応する答えを選択してください:
-
animals.csv
の最初の3行 -
animals.csv
の最後の2行 -
animals.csv
の最初の3行と最後の2行 -
animals.csv
の2行目と3行目
正解は選択肢3です。
選択肢1が正しい場合は、head
コマンドのみを実行します。
選択肢2が正しい場合は、tail
コマンドのみを実行します。
選択肢4が正しい場合は、次のようにhead
の出力をtail
にパイプで渡す必要があります:
head -n 3 animals.csv | tail -n 2 > animals-subset.csv
出力を別のコマンドに渡す
行数が最も少ないファイルを見つける例では、
出力を保存するためにlengths.txt
とsorted-lengths.txt
という
2つの中間ファイルを使用しています。
しかし、これでは操作がわかりにくくなります。
wc
、sort
、head
が何をするのか理解していても、
中間ファイルの存在が混乱を招きます。
これを簡単に理解できるように、sort
とhead
を連続して実行します:
出力
9 methane.pdb
垂直バー|
はパイプと呼ばれます。
これは、左側のコマンドの出力を右側のコマンドの入力として使用することをシェルに指示します。
これにより、sorted-lengths.txt
ファイルは不要になりました。
複数のコマンドを組み合わせる
パイプを連続してチェーンすることも可能です。
たとえば、wc
の出力を直接sort
に送り、
さらにその結果をhead
に送ることができます。
これにより、中間ファイルは一切不要になります。
まず、パイプを使用してwc
の出力をsort
に送ります:
出力
9 methane.pdb
12 ethane.pdb
15 propane.pdb
20 cubane.pdb
21 pentane.pdb
30 octane.pdb
107 total
次に、その出力をhead
に送ることで、完全なパイプラインは次のようになります:
出力
9 methane.pdb
これは、数学者がlog(3x)のように関数をネストして、
「3xの対数」と説明するのと同じです。 私たちの場合、アルゴリズムは
「*.pdb
の行数をソートして先頭を取得する」というものです。
上記のコマンドで使用されたリダイレクトとパイプの概念を以下に示します:
コマンドをパイプでつなぐ
現在のディレクトリ内で、行数が最も少ない3つのファイルを見つけたいとします。 以下のどのコマンドが機能するでしょうか?
wc -l * > sort -n > head -n 3
wc -l * | sort -n | head -n 1-3
wc -l * | head -n 3 | sort -n
wc -l * | sort -n | head -n 3
正解は選択肢4です。 パイプ文字|
は、あるコマンドの出力を
別のコマンドの入力として接続するために使用されます。
>
は標準出力をファイルにリダイレクトします。
shell-lesson-data/exercise-data/alkanes
ディレクトリで試してみてください!
協力するために設計されたツール
このプログラムをつなげて使うというアイデアこそが、Unixが成功した理由です。
多くの異なるタスクを実行する巨大なプログラムを作成する代わりに、
Unixのプログラマーは、各ツールが1つの仕事をうまくこなし、
お互いにうまく連携できるようにすることに集中します。
このプログラミングモデルは「パイプとフィルタ」と呼ばれます。
私たちはすでにパイプを見てきました。
フィルタとは、wc
やsort
のように、
入力ストリームを出力ストリームに変換するプログラムのことです。
標準的なUnixツールのほぼすべてがこのように動作します。
特別な指示がない限り、 これらは標準入力から読み取り、
読み取った内容を処理し、 標準出力に書き出します。
標準入力からテキストの行を読み取り、 標準出力にテキストの行を書き出すプログラムは、 同じように振る舞う他のすべてのプログラムと組み合わせることができます。 プログラムをこのように作成することで、 自分自身や他の人々がそれらをパイプラインに組み込んで その能力を何倍にも拡張できるようにするべきです。
パイプを読む
ファイルanimals.csv
(shell-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
コマンドは逆順にソートします。
ヒント:理解をテストするために、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
を使用して、次のコマンドを考えます:
cut
コマンドは、ファイル内の各行から特定の部分を削除または「切り取る」ために使用されます。
cut
は、行がTab文字で区切られていることを前提としています。
このように使用される文字は区切り文字と呼ばれます。
上記の例では、-d
オプションを使用してカンマを区切り文字として指定しています。
また、-f
オプションを使用して、2番目のフィールド(列)を抽出することを指定しています。
これにより、次の出力が得られます:
出力
deer
rabbit
raccoon
rabbit
deer
fox
rabbit
bear
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
であると仮定して、
ファイル内の各動物の合計数を示すテーブルを生成するには、
どのコマンドを使用しますか?
sort animals.csv | uniq -c
sort -t, -k2,2 animals.csv | uniq -c
cut -d, -f 2 animals.csv | uniq -c
cut -d, -f 2 animals.csv | sort | uniq -c
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
ディレクトリから次のコマンドを入力します:
出力は以下のように18行が表示されます:
出力
300 NENE01729A.txt
300 NENE01729B.txt
300 NENE01736A.txt
300 NENE01751A.txt
300 NENE01751B.txt
300 NENE01812A.txt
... ...
次に以下を入力します:
出力
240 NENE02018B.txt
300 NENE01729A.txt
300 NENE01729B.txt
300 NENE01736A.txt
300 NENE01751A.txt
おっと、1つのファイルが他より60行少ないことに気づきました。
ログを確認すると、そのアッセイは月曜日の朝8時に実行されており、週末に誰かが機械を使用し、リセットするのを忘れた可能性があります。
そのサンプルを再実行する前に、データが多すぎるファイルがないか確認します:
出力
300 NENE02040B.txt
300 NENE02040Z.txt
300 NENE02043A.txt
300 NENE02043B.txt
5040 total
これらの数値は問題なさそうですが、3行目の「Z」は何でしょうか?
彼女のサンプルはすべて「A」または「B」でマークされているはずです。
慣例として、彼女の研究室では「Z」を欠損情報があるサンプルを示すために使用します。
これに似た他のサンプルを探すために、次のコマンドを実行します:
出力
NENE01971Z.txt NENE02040Z.txt
やはり、彼女がラップトップのログを確認すると、これらのサンプルの深度が記録されていません。
他の方法で情報を取得するには遅すぎるため、これら2つのファイルを解析から除外する必要があります。rm
を使用して削除することもできますが、深度が重要でない解析を行う可能性もあるため、代わりに後でワイルドカード式
NENE*A.txt NENE*B.txt
を使ってファイルを選択するよう注意する必要があります。
不要なファイルの削除
ストレージを節約するために、処理済みデータファイルを削除し、生データファイルと処理スクリプトのみを保持したいとします。
生データファイルは .dat
で終わり、処理済みファイルは
.txt
で終わります。
次のうち、処理済みデータファイルのみを削除するコマンドはどれでしょうか?
rm ?.txt
rm *.txt
rm * .txt
rm *.*
- このコマンドは1文字の名前を持つ
.txt
ファイルを削除します。 - これが正しい答えです。
- シェルは
*
を現在のディレクトリ内のすべてのファイルに展開するため、このコマンドはすべての一致するファイルと追加の.txt
という名前のファイルを削除しようとします。 - シェルは
*.*
を拡張して少なくとも1つの.
を含むすべてのファイル名に一致させるため、処理済みファイル (.txt
) と生データファイル (.dat
) の両方が削除されます。
まとめ
-
wc
は入力内の行、単語、および文字数をカウントします。 -
cat
は入力の内容を表示します。 -
sort
は入力をソートします。 -
head
はデフォルトで入力の最初の10行を表示します。 -
tail
はデフォルトで入力の最後の10行を表示します。 -
command > [file]
はコマンドの出力をファイルにリダイレクトします(既存の内容を上書きします)。 -
command >> [file]
はコマンドの出力をファイルに追記します。 -
[first] | [second]
はパイプラインです:最初のコマンドの出力が2番目のコマンドの入力として使用されます。 - シェルを使用する最良の方法は、単純で一つの目的を持つプログラム(フィルタ)をパイプで組み合わせることです。