Linux の 101 試験対策: テキスト・ストリームとフィルター

コマンドラインで GNU テキスト・ユーティリティーを使ってテキストを操作する

概要: テキストの操作には、カット・アンド・ペースト以外にもたくさんありますが、特に GUI を使用していないとなれば、テキスト操作の数の多さは尚更のことです。テキスト操作について、Linux Professional Institute Certification (LPIC) 101 試験に備えるため、または自ら活用するために学んでください。この記事では、Ian Shields が GNU テキスト・ユーティリティー・パッケージに含まれるフィルターを使用して Linux® でテキストを操作する方法を手ほどきします。この記事を読み終わる頃には、エキスパートのごとくテキストを操作できるようになっているはずです。[注意深い読者からご指摘いただき、リスト 7 の最初の行を修正しました (編集者より)]

Ian Shields, Senior Programmer, IBM

Ian ShieldsIan Shields は、developerWorks Linux ゾーンの様々な Linux プロジェクトに関わっています。彼はノースキャロライナ州 Research Triangle Park にある IBM のシニア・プログラマーです。1973年にオーストラリアのキャンベラでシステム・エンジニアとして IBM に入社して以来、カナダのモントリオールやノースキャロライナ州 Research Triangle Park で、コミュニケーション・システムやパーベイシブ・コンピューティングに携わってきました。彼はいくつかの特許を保持しています。Australian National University にて純粋数学および哲学で学位を取得し、また North Carolina State University にてコンピューター・サイエンスで修士号と博士号を取得しています。Ian について詳しく知るには、My developerWorks で彼のプロフィールを見てください。


developerWorks 貢献著者レベル

2010年 1月 26日 (初版 2009年 8月 26日)

この連載について

この連載は Linux システム管理タスクの学習に役立つだけでなく、LPIC-1 (Linux Professional Institute Certification レベル 1) 試験に備えるための教材にもなります。

連載の各記事についての説明とリンクについては、連載のロードマップを参照してください。現在進行中のこのロードマップは、LPIC-1 試験の最新の目標 (2009年4月) を反映しています。完成した記事はその都度ロードマップに追加されていきますが、当面は developerWorks の LPI 認定試験対策チュートリアルで同様の教材の以前のバージョンを調べてください。これらのバージョンは、2009年4月より前の LPIC-1 目標に対応しています。

前提条件

この連載の記事を最大限に活用するには、Linux の基礎知識と、記事に記載されたコマンドを演習できる実際の Linux システムが必要です。プログラムのバージョンによって出力のフォーマットに違いが出てくる場合もあるため、コマンドの実行結果は必ずしもここに記載するリストや図とまったく同じであるとは限りません。

概要

この記事ではフィルターについて取り上げ、皆さんがフィルターを使ってテキストを操作するための複合パイプラインを作成できるようにします。テキストの表示、ソート、単語数と行数のカウント、文字の変換などのタスクを行う方法を学ぶとともに、ストリーム・エディター sed の使い方も学んでください。

この記事では、以下のトピックを取り上げます。

  • テキスト・ファイルや出力ストリームをテキスト・ユーティリティー・フィルターに送り込んで出力を変更する方法
  • GNU テキスト・ユーティリティー・パッケージに含まれる標準 UNIX コマンドの使用方法
  • sed エディターを使用して、テキスト・ファイルに対する複雑な変更スクリプトを作成する方法

この記事は、Junior Level Administration (LPIC-1) 101 試験における主題 103の 103.2 の試験対策となります。この目標の重要度は 3 です。この記事の内容は、2009年4月時点での 101 試験の目標に対応します。明確な要件については、必ず目標を参照して確認してください。


テキストのフィルタリング

テキストのフィルタリングとは、テキストの入力ストリームを取り、そのテキストに対して何らかの変換を行ってから出力ストリームに送信するプロセスのことです。入力元または出力先がファイルである場合もありますが、Linux および UNIX® 環境でのフィルタリングは大抵の場合、コマンドのパイプラインを構成することによって行われます。コマンドのパイプラインでは、1 つのコマンドの出力を次のコマンドの入力として使用するために、出力がパイプされるか、またはリダイレクトされます。パイプとリダイレクトについてはストリーム、パイプ、リダイレクトに関する記事 (連載のロードマップを参照) で詳細に説明しますが、差し当たりここでは、パイプと基本的な出力のリダイレクトとして | 演算子と > 演算子を使用する方法について説明します。

ストリーム

ストリームとは、ライブラリー関数を使用して読み取りまたは書き込みすることができるバイトのシーケンスにすぎません。ライブラリー関数は、ベースにあるデバイスの詳細をアプリケーションから隠します。そのためプログラムはストリームを使用することによって、デバイスに依存することなく端末、ファイル、またはネットワーク・ソケットのいずれに対しても読み取り/書き込みを行うことができます。最近のプログラミング環境とシェルが使用する標準入出力ストリームは以下の 3 つです。

  • stdin。コマンドに入力を提供する標準入力ストリームです。
  • stdout。コマンドからの出力を表示する標準出力ストリームです。
  • stderr。コマンドからのエラー出力を表示する標準エラー・ストリームです。

| によるパイプ

入力はコマンドに指定したパラメーターから渡すことが可能で、出力はユーザーの端末に表示することが可能です。多くのテキスト処理コマンド (フィルター) は、標準入力ストリームからでも、ファイルからでも入力を取ることができます。例えばコマンド command1 の出力をフィルター command2 への入力として使用するには、この 2 つのコマンドをパイプ演算子 (|) を使って結合します。リスト 1 に、短い単語のリストをソートするために echo コマンドの出力をパイプする方法を示します。

リスト 1. echo コマンドの出力と sort コマンドの入力とのパイプ
[ian@echidna ~]$ echo -e "apple\npear\nbanana"|sort
apple
banana
pear

上記のいずれのコマンドにしても、オプションや引数を指定することができます。また、| を使ってパイプラインの 2 番目のコマンドからの出力を 3 番目のコマンドにリダイレクトすることや、さらにその出力を次のコマンドの入力にするといったことを繰り返すことができます。それぞれに限られた機能を持つコマンドをいくつも接続して長いパイプラインを構成するという方法は、Linux および UNIX ではタスクを実現するための一般的な方法です。コマンドへの引数としてファイル名の代わりにハイフン (-) が使用されていることもよくあります。これは、入力はファイルからでなく、stdin から渡されるという意味です。

> による出力のリダイレクト

複数のコマンドからなるパイプラインを作成し、端末上に出力を表示するという機能は便利ですが、出力をファイルに保存したいという場合もよくあります。その場合には、出力リダイレクト演算子 (>) を使用します。

このセクションでは以降、小さなサンプル・ファイルを用いて説明を進めるので、lpi103-2 というディレクトリーを作成し、cd を実行してそのディレクトリーに移動してください。続いて > を使って echo コマンドの出力を text1 という名前のファイルにリダイレクトします。この一連の操作をリスト 2 に記載します。出力はファイルにリダイレクトされているため、端末には表示されないことに注意してください。

リスト 2. コマンド出力のファイルへのリダイレクト
[ian@echidna ~]$ mkdir lpi103-2
[ian@echidna ~]$ cd lpi103-2
[ian@echidna lpi103-2]$ echo -e "1 apple\n2 pear\n3 banana" > text1

これで、パイプライン処理とリダイレクトを行うための基本的なツールを使えるようになったので、ここからはよく使われる UNIX および Linux のテキスト処理コマンドとフィルターに目を向けます。これらのセクションでは基本的な機能を抜粋して説明します。コマンドの詳細については該当する man ページを参照してください。


cat、od、split

test1 ファイルを作成したところで、このファイルの内容を確認してみてください。ファイルの内容を stdout に表示するには、cat (concatenate) (連結) の略) コマンドを使用します。リスト 3 で、上記で作成したファイルの内容を確認します。

リスト 3. cat によるファイルの内容の表示
[ian@echidna lpi103-2]$ cat text1
1 apple
2 pear
3 banana

ファイル名が指定されていない場合 (またはファイル名として - を指定した場合)、cat コマンドは stdin から入力を取ります。今度はこのコマンドと出力リダイレクトを併せて使用して、別のテキスト・ファイルを作成します (リスト 4 を参照)。

リスト 4. cat によるテキスト・ファイルの作成
[ian@echidna lpi103-2]$ cat >text2
9       plum
3       banana
10      apple

その他多くの小さなフィルター

小さなフィルターとしては、tac コマンドもあります。これは cat を逆にした名前で、その機能についてもファイルの内容が逆順に表示されるという点で cat とは逆です。
試しに tac text2 text1 を実行してみてください。

リスト 4 の cat は、ファイルの終わりに達するまで stdin からの読み取りを続けます。ファイルの終わりを通知するには、Ctrl-d (Ctrl を押しながら d を押すこと) の組み合わせを使用します。これは、bash シェルを終了するときと同じキーの組み合わせです。フルーツの名前を整列させるには、Tab キーを使用してください。

前述のとおり、catconcatenate (連結) の略です。つまり cat を使えば、複数のファイルを連結して表示することができます。リスト 5 には、これまでに作成した 2 つのファイルの両方が表示されています。

リスト 5. cat による 2 つのファイルの連結
[ian@echidna lpi103-2]$ cat text*
1 apple
2 pear
3 banana
9       plum
3       banana
10      apple

2 つのサンプル・テキスト・ファイルを cat を使って表示すると、内容が表示される位置が違うことに気付くはずです。この違いをもたらす原因について学ぶには、ファイル内にある制御文字を調べる必要があります。制御文字は、それ自体が何らかの表現で表示されるのではなく、出力されるテキストの表示に作用します。したがって、これらの特殊文字を見つけて解釈するには、特殊文字が表現される形式でファイルをダンプしなければなりません。GNU テキスト・ユーティリティーには、そのためのコマンド、od (Octal Dump (8 進ダンプ)) があります。

od には、ファイル・オフセットの基数を制御する -A オプションや、ファイルの内容を表示する際の書式を制御する -t オプションなど、複数のオプションがあります。オフセットの基数には、o (8 進数、これがデフォルトです)、d (10 進数)、x (16 進数)、または n (オフセット非表示) を指定することができます。出力は、8 進数、16 進数、10 進数、浮動小数点、バックスラッシュ・エスケープを使用した ASCII、または名前の付けられた文字 (改行には nl、水平タブには ht など) として表示することができます。リスト 6 に、サンプル・ファイル text2 をダンプする場合に使用できる書式をいくつか記載します。

リスト 6. od によるファイルのダンプ
[ian@echidna lpi103-2]$ od text2
0000000 004471 066160 066565 031412 061011 067141 067141 005141
0000020 030061 060411 070160 062554 000012
0000031
[ian@echidna lpi103-2]$ od -A d -t c text2
0000000   9  \t   p   l   u   m  \n   3  \t   b   a   n   a   n   a  \n
0000016   1   0  \t   a   p   p   l   e  \n
0000025
[ian@echidna lpi103-2]$ od -A n -t a text2
   9  ht   p   l   u   m  nl   3  ht   b   a   n   a   n   a  nl
   1   0  ht   a   p   p   l   e  nl

注:

  1. cat-A オプションは、タブと行の終わりを確認する手段にもなります。詳細については、man ページを参照してください。
  2. text2 ファイルでタブの代わりにスペースが使われている場合は、この後の「expand、unexpand、tr」のセクションで、ファイル内でのタブとスペースを切り替える方法を読んでください。
  3. メインフレームの知識がある場合は、別のユーティリティー・セットに含まれている hexdump ユーティリティーを使うこともできます。ここではこのユーティリティーについて説明しないので、man ページを参照してください。

私たちが使用しているサンプル・ファイルは非常に小さなものですが、ファイルのサイズが大きいために、いくつかの部分に分割しなければならないという場合もあります。例えば、DVD を作成してもらうためにサイズの大きなファイルを CD サイズのチャンクに分割し、それを CD に書き込んで送り届けたい場合があるかもしれません。このタスクを引き受けるのは、split コマンドです。cat コマンドを使ってファイルを簡単に作り直せるように、split コマンドでも簡単にファイルを分割することができます。デフォルトでは、split コマンドによって処理されたファイルの名前には、「x」というプレフィックスと「aa」、「ab」、「ac」… 「ba」、「bb」などのサフィックスが付加されます。これらのデフォルト設定はオプションで変更することができます。また、出力ファイルのサイズを制御できることに加え、出力ファイルに完全な行を書き込むか、あるいはバイト数だけを書き込むかを制御することもできます。

リスト 7 は、2 つのサンプル・テキスト・ファイルを分割し、出力ファイルにデフォルトとは異なるプレフィックスを付ける例です。text1 は最大で 2 行が含まれるファイルに分割し、text2 は最大 18 バイトが含まれるファイルに分割します。次に分割された部分を、cat を使用して個別に表示するとともに、グロビング (globbing) を使用して完全なファイルを表示します。グロビングについては、基本的なファイルおよびディレクトリー管理に関する記事 (連載のロードマップを参照) で説明します。

リスト 7. split と cat による分割と再結合
[ian@echidna lpi103-2]$ split -l 2 text1
[ian@echidna lpi103-2]$ split -b 17 text2 y
[ian@echidna lpi103-2]$ cat yaa
9       plum
3       banana
1[ian@echidna lpi103-2]$ cat yab
0       apple
[ian@echidna lpi103-2]$ cat y* x*
9       plum
3       banana
10      apple
1 apple
2 pear
3 banana

分割後の yaa という名前のファイルは改行文字で終わっていないため、cat を使ってファイルを表示した後にプロンプトがオフセットされたことに注意してください。


wc、head、tail

cat はファイル全体を表示します。ここでのサンプル・ファイルのように小さなファイルであればファイル全体を表示しても問題ありませんが、ファイルのサイズが大きい場合を考えてみてください。その場合、まずは wc (Word Count) コマンドを使って、ファイルの大きさを確認するとよいでしょう。wc コマンドはファイルに含まれる行数、単語数、およびバイト数を表示します。バイト数を調べるには、ls -l を使用するという方法もあります。リスト 8 に、2 つのサンプル・テキスト・ファイルの詳細な一覧表示に続き、wc による出力を記載します。

リスト 8. テキスト・ファイルでの wc の使用
[ian@echidna lpi103-2]$ ls -l text*
-rw-rw-r--. 1 ian ian 24 2009-08-11 14:02 text1
-rw-rw-r--. 1 ian ian 25 2009-08-11 14:27 text2
[ian@echidna lpi103-2]$ wc text*
 3  6 24 text1
 3  6 25 text2
 6 12 49 total

オプションで、wc の出力を制御することや、行の最大長などの情報も表示することができます。詳細については、man ページを参照してください。

ファイルの最初の部分 (先頭) または最後の部分 (末尾) を表示するには、それぞれ head コマンドと tail コマンドを使用することができます。この 2 つのコマンドは、フィルターとして使用することも、ファイル名を引数として取ることもできます。デフォルトでは、ファイルまたはストリームの最初、または最後の 10 行を表示します。リスト 9 ではシステム起動時のメッセージを表示する dmesg コマンドを wctail、および head と組み合わせて使用することで、791 個のメッセージがあることを検出した後、そのうちの最後の 10 個メッセージを表示し、さらに最後から 15 番目以降の 6 個のメッセージを表示しています。この出力で一部の行を省略する場合は、… で示してあります。

リスト 9. wc、head、および tail によるブート・メッセージの表示
[ian@echidna lpi103-2]$ dmesg|wc
    791    5554   40186
[ian@echidna lpi103-2]$ dmesg | tail
input: HID 04b3:310b as /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2.4/3-2.4:1.0/input/i
nput12
generic-usb 0003:04B3:310B.0009: input,hidraw1: USB HID v1.00 Mouse [HID 04b3:310b] on us
b-0000:00:1a.0-2.4/input0
usb 3-2.4: USB disconnect, address 11
usb 3-2.4: new low speed USB device using uhci_hcd and address 12
usb 3-2.4: New USB device found, idVendor=04b3, idProduct=310b
usb 3-2.4: New USB device strings: Mfr=0, Product=0, SerialNumber=0
usb 3-2.4: configuration #1 chosen from 1 choice
input: HID 04b3:310b as /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2.4/3-2.4:1.0/input/i
nput13
generic-usb 0003:04B3:310B.000A: input,hidraw1: USB HID v1.00 Mouse [HID 04b3:310b] on us
b-0000:00:1a.0-2.4/input0
usb 3-2.4: USB disconnect, address 12
[ian@echidna lpi103-2]$ dmesg | tail -n15 | head -n 6
usb 3-2.4: USB disconnect, address 10
usb 3-2.4: new low speed USB device using uhci_hcd and address 11
usb 3-2.4: New USB device found, idVendor=04b3, idProduct=310b
usb 3-2.4: New USB device strings: Mfr=0, Product=0, SerialNumber=0
usb 3-2.4: configuration #1 chosen from 1 choice
input: HID 04b3:310b as /devices/pci0000:00/0000:00:1a.0/usb3/3-2/3-2.4/3-2.4:1.0/input/i
nput12

tail の一般的な使用法としては、-f オプション (通常は行数を 1 に設定) を使ってファイルを追跡するという使い方もあります。この方法は、バックグラウンド・プロセスがファイルに出力を生成している場合、ファイルの内容を調べてプロセスの実行状況を確認するために使用することができます。このモードでは、tail はキャンセル (Ctrl-c を使用) されるまで実行され、ファイルに行が書き込まれと、その行を表示します。


expand、unexpand、tr

サンプル・ファイルの text1 と text2 を作成したときに、text2 はタブ文字を使って作成しましたが、タブをスペースに変換したり、あるいはその逆の変換を行ったりする必要が出てくる場合もあります。この双方のタスクに対応するのが、expand および unexpand コマンドです。どちらのコマンドでも、-t オプションを使ってタブ・ストップ数を設定することができます。-t に続く単一の値によって、タブが繰り返されるときの間隔が設定されるのです。リスト 10 に、text2 のタブのそれぞれを 1 つのスペースに展開する方法、そして text2 のテキストのアラインメントを解除する expandunexpand のちょっと変わったシーケンスを示します。

リスト 10. expand と unexpand の使用
[ian@echidna lpi103-2]$ expand -t 1 text2
9 plum
3 banana
10 apple
[ian@echidna lpi103-2]$ expand -t8 text2|unexpand -a -t2|expand -t3
9           plum
3           banana
10       apple

残念ながら、unexpand を使って text1 のスペースをタブに置換することはできません。unexpand ではタブに変換するにはスペースが 2 つ以上必要なためです。ただし、tr コマンドを使用して 1 つの文字セット (set1) 内の文字を別の文字セット (set2) 内の対応する文字に変換することはできます。リスト 11 に、tr を使用してスペースをタブに変換する方法を示します。tr は単なるフィルターであるため、このコマンドの入力は cat コマンドを使って生成します。この例では、- を使用して cat に対して標準入力を指定し、tr の出力と text2 ファイルを連結できるようにする方法もわかります。

リスト 11. tr の使用
[ian@echidna lpi103-2]$ cat text1 |tr ' ' '\t'|cat - text2
1       apple
2       pear
3       banana
9       plum
3       banana
10      apple

上記の 2 つの例の内容がよくわからない場合は、od を使ってパイプラインの各ステージを順に終了してみてください。以下はその一例です。
cat text1 |tr ' ' '\t' | od -tc


pr、nl、fmt

pr コマンドは、印刷用にファイルの書式を設定するために使用します。デフォルトのヘッダーにはファイル名とファイルの作成日時、そしてページ番号と 2 行の空白のフッターが組み込まれます。複数のファイルや標準入力ストリームから出力が作成される場合には、ファイル名と作成日の代わりに現在の日時が使用されます。複数のファイルを並べて出力し、オプションを使って書式設定のさまざまな側面を制御することができます。詳細については、同じく man ページを参照してください。

行に番号を付ける nl コマンドは、ファイルを印刷するときに便利です。また、cat コマンドの -n オプションで行に番号を付けることもできます。リスト 12 に、サンプル・テキスト・ファイルの印刷方法に続き、text2 に番号を付けて text1 と並べて印刷する方法を示します。

リスト 12. 印刷用の行番号と書式の設定
[ian@echidna lpi103-2]$ pr text1 | head


2009-08-11 14:02                      text1                       Page 1


1 apple
2 pear
3 banana


[ian@echidna lpi103-2]$ nl text2 | pr -m - text1 | head


2009-08-11 15:36                                                  Page 1


     1  9       plum                        1 apple
     2  3       banana                      2 pear
     3  10      apple                       3 banana

テキストの書式を設定するには、fmt コマンドも役立ちます。このコマンドは、テキストが余白の中に収まるように書式を設定します。複数の短い行を結合することも、長い行を分割することもできます。リスト 13 では、!#:* 履歴機能のバリエーションを使ってセンテンスの入力を 4 回保存し、1 行の長いテキストが含まれる text3 を作成します。また、1 行あたり 1 つの単語が含まれる text4 も作成します。その後、cat を使用して、行の終わりを示す「$」文字も含め、書式が設定されていない状態でファイルの内容を表示します。最後に fmt を使用して各行が最大 60 文字になるように書式を設定します。その他のオプションについては、同じく man ページで詳細を調べてください。

リスト 13. 行の最大長に従った書式設定
[ian@echidna lpi103-2]$ echo "This is a sentence. " !#:* !#:1->text3
echo "This is a sentence. " "This is a sentence. " "This is a sentence. ">text3
[ian@echidna lpi103-2]$ echo -e "This\nis\nanother\nsentence.">text4
[ian@echidna lpi103-2]$ cat -et text3 text4
This is a sentence.  This is a sentence.  This is a sentence. $
This$
is$
another$
sentence.$
[ian@echidna lpi103-2]$ fmt -w 60 text3 text4
This is a sentence.  This is a sentence.  This is a
sentence.
This is another sentence.

sort と uniq

sort コマンドは、システムのロケールに対応する照合順序 (LC_COLLATE) で入力をソートします。sort コマンドではソート済みのファイルをマージしたり、ファイルがソートされているかどうかをチェックしたりすることもできます。

リスト 14 は、text1 の空白をタブに変換してから、sort コマンドを使って 2 つのサンプル・テキスト・ファイルをソートする例です。ソート順は文字順となっていることから、意外な結果になる場合もあります。しかし幸い、sort コマンドは数値によっても、文字の値によってもソートすることができます。さらに、選択したソートの基準をレコード全体に指定することも、フィールドごとに指定することもできます。別のフィールド区切り文字を指定しない限り、フィールドは空白またはタブで区切られます。リスト 14 の 2 番目の例では、最初のフィールドを数値でソートし、2 番目のフィールドを照合順序 (アルファベット順) でソートします。また、この例では -u オプションを使用して重複する行を排除し、それぞれに固有の行のみとなるようにしています。

リスト 14. 文字と数値によるソート
[ian@echidna lpi103-2]$ cat text1 | tr ' ' '\t' | sort - text2
10      apple
1       apple
2       pear
3       banana
3       banana
9       plum
[ian@echidna lpi103-2]$ cat text1|tr ' ' '\t'|sort -u -k1n -k2 - text2
1       apple
2       pear
3       banana
9       plum
10      apple

フルーツ「apple」が含まれる行がまだ 2 つ残っていることに注目してください。これは、この例では k1n と k2 という 2 つのソート・キーの両方で一意性がテストされるためです。上記のパイプラインのステップを変更または追加して、2 番目の「apple」のオカレンスを排除するにはどうすればよいでしょうか。

重複する行の排除を制御するには、uniq という別のコマンドを使用することができます。uniq コマンドは通常、ソート済みのファイルに対して実行されますが、ファイルがソートされているかどうかに関わらず、連続する同一の行をファイルから削除します。uniq コマンドは一部のフィールドを無視することもできます。リスト 15 では、2 番目のフィールド (フルーツの名前) を使って 2 つのテキスト・ファイルをソートした上で、2 番目のフィールド以降が同一になっている行を排除します (つまり、uniq でテストする際に最初のフィールドをスキップするということです)。

リスト 15. uniq の使用
[ian@echidna lpi103-2]$ cat text1|tr ' ' '\t'|sort -k2 - text2|uniq -f1
10      apple
3       banana
2       pear
9       plum

この例では照合順序でソートされることから、uniq は「1 apple」の行ではなく「10 apple」の行を残します。キー・フィールド 1 に数値順のソートを追加して、結果がどのように変わるか確かめてみてください。


cut、paste、join

テキスト・データのフィールドを操作するコマンドをあと 3 つ説明します。これらのコマンドは、表形式のデータを操作する際に特に役立ちます。まず始めに、cut コマンドから見ていきましょう。このコマンドは、テキスト・ファイルからフィールドを抽出します。デフォルトのフィールド区切り文字はタブ文字です。リスト 16 では、cut コマンドを使用して text2 の 2 つの列を切り離し、出力区切り文字としてスペースを使用しています。この方法は、各行のタブをスペースに変換する風変わりな方法です。

リスト 16. cut の使用
[ian@echidna lpi103-2]$ cut -f1-2 --output-delimiter=' ' text2
9 plum
3 banana
10 apple

pr コマンドに -m オプションを指定してファイルをマージするのと同じように、paste コマンドは複数のファイルからの行を横に並べて貼り付けます。リスト 17 に、2 つのサンプル・テキスト・ファイルを貼り付けた結果を示します。

リスト 17. ファイルの貼り付け
[ian@echidna lpi103-2]$ paste text1 text2
1 apple 9       plum
2 pear  3       banana
3 banana        10      apple

上記は単純な貼り付け操作の例ですが、paste では 1 つ以上のファイルのデータを何通りもの方法で貼り付けることができます。詳細については、man ページを参照してください。

フィールドを操作する最後のコマンドとして説明するのは、一致するフィールドをベースに複数のファイルを結合する join です。このコマンドの場合、対象ファイルの結合されるフィールドはソートされていなければなりません。text2 は数値順にソートされていないので、このファイルをソートしてから結合すると、一致する結合フィールド (この例の場合、値が 3 のフィールド) を持つ 2 つの行が結合されることになります。

リスト 18. 結合フィールドによるファイルの結合
[ian@echidna lpi103-2]$ sort -n text2|join -j 1 text1 -
3 banana banana
join: file 2 is not in sorted order

上記で何が誤っているかと言うと、「sort と uniq」のセクションで説明した文字および数値によるソートを思い出してください。結合は、ロケールの照合順序に従って、一致する文字に対して行われます。したがって、フィールドの長さがすべて同じでない限り、数値フィールドでは機能しません。

上記では、-j 1 オプションを指定して各ファイルのフィールド 1 で結合するようにしています。結合対象として使用するフィールドは、ファイルごとに指定できます。つまり、例えば一方のファイルではフィールド 3 で、他方のファイルではフィールド 10 で結合することも可能です。

ここで新しく text5 ファイルを作成し、2 番目のフィールド (フルーツの名前) で text1 をソートしてから、スペースをタブに置き換えてください。次に text2 を 2 番目のフィールドでソートしてから、それぞれのファイルの 2 番目のフィールドを結合フィールドとして使用して text 5 と結合すると、2 つの項目 (apple と banana) が一致するはずです。リスト 19 に、この場合の結合を示します。

リスト 19. 結合フィールドによるファイルの結合
[ian@echidna lpi103-2]$ sort -k2 text1|tr ' ' '\t'>text5
[ian@echidna lpi103-2]$ sort -k2 text2 | join -1 2 -2 2 text5 -
apple 1 10
banana 3 3

sed

sed (stream editor) は、数多くの本、および本の章で取り上げられているだけでなく、いくつかの developerWorks の記事でも取り上げられているストリーム・エディターです (「参考文献」を参照)。sed は極めて強力であり、このエディターで実現可能な内容を制限するのはユーザーの想像力以外にはありません。この短いセクションの目的は sed について完全に、あるいは広範に説明することではなく、皆さんに sed への興味を持ってもらうことです。

これまで見てきたテキスト・コマンドの多くと同じく、sed はフィルターとして機能することも、ファイルからその入力を取ることもできます。出力は標準出力ストリームに送られます。sed は入力から渡された行をパターン・スペース (pattern space) にロードし、sed の編集コマンドをパターン・スペースの内容に適用してからそのパターン・スペースを標準出力に書き込みます。sed ではパターン・スペースの中で複数の行を結合することも、パターン・スペースの内容をファイルに書き込むことも、選択された出力だけを書き込むことも、書き込みを行わないことも可能です。

sed は、パターン・スペースでのテキストの検索や、選択的なテキスト置換、そして一連の編集コマンドの操作対象とするテキスト行の制御に、正規表現の構文を使用します。正規表現については、正規表現を使用したテキスト・ファイルの検索についての記事で詳しく説明します (連載のロードマップを参照)。テキストはホールド・バッファー (hold buffer) に一時的に保存されます。ホールド・バッファーをパターン・スペースの代わりにすることも、ホールド・バッファーをパターン・スペースに追加したり、パターン・スペースとの間でホールド・バッファーを交換したりすることも可能です。sed には限られた数のコマンドしかありませんが、これらのコマンドを正規表現の構文およびホールド・バッファーと組み合わせることで、さまざまな驚くべき機能が実現されます。一連の sed コマンドは通常、sed スクリプトと呼ばれます。

リスト 20 に、3 つの単純な sed スクリプトを記載します。最初の sed スクリプトでは、s (substitute (置換)) コマンドを使用して各行で小文字の「a」を大文字に置換します。この例では最初の「a」しか置換しませんが、2 番目の例には「g」(global (グローバル) の略) フラグを追加し、sed がすべてのオカレンスを変更するようにします。3 番目のスクリプトではさらに行を削除する d (delete (削除)) コマンドを追加し、アドレスを 2 に設定して 2 行だけが削除されるようにしています。各コマンドをセミコロン (;) で区切り、2 番目のスクリプトで使用したグローバル置換を使用して「a」を「A」に置換します。

リスト 20. sed スクリプトの開始
[ian@echidna lpi103-2]$ sed 's/a/A/' text1
1 Apple
2 peAr
3 bAnana
[ian@echidna lpi103-2]$ sed 's/a/A/g' text1
1 Apple
2 peAr
3 bAnAnA
[ian@echidna lpi103-2]$ sed '2d;$s/a/A/g' text1
1 apple
3 bAnAnA

sed は個別の行を操作するだけでなく、複数行の範囲を操作することもできます。範囲の開始と終わりはカンマ (,) で区切った行番号または正規表現で指定します。範囲の終わりには、ファイルの終わりを示すドル記号 ($) を指定することも可能です。アドレスまたはアドレスの範囲を指定する場合、複数のコマンドを中括弧 ({ と }) で囲んでグループ化し、そのコマンドのグループを範囲で選択した行にだけ適用することができます。リスト 21 に、ファイルの最後の 2 行にのみグローバル置換を適用する 2 つの方法を示します。このリストには、複数のコマンドをスクリプトに追加する -e オプションの使い方も示されています。

リスト 21. sed アドレス
[ian@echidna lpi103-2]$ sed -e '2,${' -e 's/a/A/g' -e '}' text1
1 apple
2 peAr
3 bAnAnA
[ian@echidna lpi103-2]$ sed -e '/pear/,/bana/{' -e 's/a/A/g' -e '}' text1
1 apple
2 peAr
3 bAnAnA

sed スクリプトはファイルに格納することもできます。実際、頻繁に使用するスクリプトはファイルに格納したくなるでしょう。前に tr コマンドを使用して text1 の空白をタブに変更したことを思い出してください。今度はこの変更を、ファイルに格納された sed で行います。echo コマンドを使用してファイルを作成すると、その結果はリスト 22 のようになります。

リスト 22. sed のワン・ライナー
[ian@echidna lpi103-2]$ echo -e "s/ /\t/g">sedtab
[ian@echidna lpi103-2]$ cat sedtab
s/ /    /g
[ian@echidna lpi103-2]$ sed -f sedtab text1
1       apple
2       pear
3       banana

リスト 22 のような便利な sed のワン・ライナーは豊富にあります。そのうちの一部については、「参考文献」のリンクを参照してください。

最後に紹介する sed の例は、= コマンドを使って行番号を出力した後、結果の出力をもう一度 sed によってフィルタリングすることで、行番号を付ける nl コマンドの効果を模倣するというものです。リスト 23 では、= コマンドを使って行番号を出力し、次に N コマンドで 2 番目の入力行をパターン・スペースに読み込んだ後、最後にパターン・スペースの 2 つの行の間にある改行文字 (\n) を削除します。

リスト 23. sed による行の番号付け
[ian@echidna lpi103-2]$ sed '=' text2
1
9       plum
2
3       banana
3
10      apple
[ian@echidna lpi103-2]$ sed '=' text2|sed 'N;s/\n//'
19      plum
23      banana
310     apple

上記は意図したとおりの結果になっていません。ここで目的としていることは、行番号を一列に並べ、ファイルからの行の前に多少のスペースを入れることです。そこで、リスト 24 では数行のコマンドを入力しています (2 番目のプロンプト > に注意してください)。この例を検討した上で、下記の説明を読んでください。

リスト 24. sed による行の番号付け (2 回目)
[ian@echidna lpi103-2]$ cat text1 text2 text1 text2>text6
[ian@echidna lpi103-2]$ ht=$(echo -en "\t")
[ian@echidna lpi103-2]$ sed '=' text6|sed "N
> s/^/      /
> s/^.*\(......\)\n/\1$ht/"
     1  1 apple
     2  2 pear
     3  3 banana
     4  9       plum
     5  3       banana
     6  10      apple
     7  1 apple
     8  2 pear
     9  3 banana
    10  9       plum
    11  3       banana
    12  10      apple

上記で行ったステップは以下のとおりです。

  1. まず cat を使用して、text1 および text2 ファイルのコピーから 12 行のファイルを作成します。桁数が異なっていなければ、列内で番号の書式を設定しても面白くありません。
  2. bash シェルは Tab キーを使用してコマンドを完了するので、本物のタブが必要なときに使用できる専用のタブ文字があると便利です。そこで echo コマンドを使用して、専用のタブ文字をシェル変数「ht」に保存します。
  3. 前に行ったように、行番号の後にデータ行が続くストリームを作成し、このストリームを sed の 2 つの目のコピーでフィルタリングします。
  4. 2 番目の行をパターン・スペースに読み込みます。
  5. パターン・スペースの先頭 (^ で指定) に行番号を追加し、6 つの空白を追加します。
  6. 次に、改行までの行のすべての部分を改行前の最後の 6 文字で置き換えてタブ文字を追加します。これで出力行の最初の 6 列で、行番号が一列に並ぶことになります。「s」コマンドの左側の部分では「\(」と「\)」を使って、右側の部分で使用する文字にマークを付けていることに注意してください。右側の部分では、このようにマークが付けられた最初のセット (この例では唯一のセット) を \1 として参照します。この例でのコマンドは、二重引用符 (") で囲まれているため、置換は $ht に対して行われることにも注意が必要です。

バージョン 4 の sed には、info 形式のドキュメントと併せて多数の優れたサンプルが含まれています。これらのサンプルは、これよりも前のバージョン 3.02 には含まれていません。GNU sed では sed --version でバージョンを表示することができます。

参考文献

学ぶために

製品や技術を入手するために

  • developerWorks から直接ダウンロードできる IBM 試用版ソフトウェアを使用して、Linux で次の開発プロジェクトを構築してください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=432963
ArticleTitle=Linux の 101 試験対策: テキスト・ストリームとフィルター
publish-date=01262010