printf ' PROLOGUE*nA 10 Ⅱ g time ago . (cat e c れ 0 ) grep awk 'BEGIN{ ORS=' getline line ; print line ; dlm=sprintf("%n") ; ile (getline line) { print dlm , line ; 解説 10 ■データの末端に余分な改行を付けないフィルター (sed の場合 ) ↓ ( 加工する。各行末に必ず改行コードがあるので、勝手に付加されない ) 第 1 章 ちょっとうれしいレシピ ー # この行が必要 多くの UNIX コマンドは改行が無いと途中のコマンドが勝手に改行を付けてしまうが、純粋なフィルターと して見た場合それでは困る。どうすればこの問題を回避できるかといえば、まず先手を打って先に改行を付けて しまう。そうすると途中に通すコマンドが勝手に改行を付けることは無くなる。そして最後に末端の改行を取り 除けばいいというわけだ。では、改行を末端に付けたり、末端から取り除いたりは具体的にどうやればいいのだ ろうか。 まず、付ける方は簡単だ。「回答」に示したコードを見れば特に説明する必要もないだろう。 一方、最後に除去をしているコードはどうやっているのか。これは AWK コマンドの性質を 1 つ利用してい る。 AWK コマンドは、 printf で改行記号 \ n を付けなかったり、組み込み変数 ORS ( 出力レコード区切り文字 ) を空にしたりすれば行末に改行コードを付けずにテキストを出力できる。後ろに追加した AWK はこの性質を 利用し、普段なら改行コードを出力した時点で行ループを区切るところを、行文字列を出力して改行コードを出 力する手前で行ループを一区切りさせるようにしてしまう。 そうすると一番最後の行のループだけは不完全になり、最後の行の文字列の後ろに改行コードが付かないこと になる。しかし、予め余分に改行を 1 個 ( つまり余分な 1 行 ) を付けておいたので、不完全になるのはその余分 な 1 行ということになる。結果、元データの末端に改行が含まれていなければ末端には改行が付かないし、あれ ば付く。 言葉では分かり難いかもしれないが、図で解説するとこんな感じだ。 -n く LF 有ったり無かったり > く LF > str_2 く LF> str_l く LF> ↓ ( 末端に改行コードを付加 ) ー n く LF 有ったり無かったり > str_2 く LF> str_l く LF> str Str
レシピ 3.5 CSV ファイルを読み込む (a) 1 行目の 3 列目の値を取得 $ . /parsrc. sh sample. csv ー grep C*IICC (b) 全ての行の 1 列目を抽出 $ . /parsrc. sh sample. csv ー grep 解説 55 ” 1 3 リ sed ) s/* [ E に ] + 3 sed , sP い ] * い ] * / / CSV ファイルは、 MicrosoftExcel とデータのやり取りをするには便利なフォーマットだが、 AWK などの標 準 UNIX コマンドで扱うにはかなり面倒だ。しかしできないわけではない。先程利用した parsrc. sh というプロ グラムのソースコードは長すぎて載せられないが、どのようにしてデータの正規化 * 4 をしたのかについて、概要 を説明することにする。 CSV ファイル (RFC 4180 ) の仕様を知る その前に、加工対象となる CSV ファイルのフォーマット仕様について知っておく必要がある。 CSV ファイ ルの構造にはいくつかの方言があるのだが、最も一般的なものが RFC 4180 で規定されている。 ExceI で扱える CSV もこの形式に準拠したものだ。主な特徴は次のとおりである。 1. 列はカンマ“ , ”で区切り、行は改行文字で区切る。 2. 値としてこれらの文字が含まれる場合は、その列全体をダブルクオーテーション“ " ”で囲む。 3. 値としてダブルクオーテーションが含まれる場合は、その列全体をダブルクオーテーション“ " ”で囲んだ うえで、値としてのダブルクオーテーション文字については 1 つにつき 2 つのダブルクオーテーションの 連続 " " で表現する。 ダブルクオーテーションが偶数個 ダブルクオーテーションが奇数個 ダブルクオーテーションが奇数個 つけだすことができる。最初に示した CSV ファイルの例をもう一度見てみよう。 この 3 番目の仕様が、 CSV パーサーを作る上で重要である。この仕様があるおかげで、値としての改行を見 cc " , d d 指す。 * 4 都合の良い形式に変換すること。この場合、 UNIX コマンドで扱い易いように「行番号、列番号、値」という並びに変換した作業を 現したら、次に奇数個の行が出現するまで、同一行と判断することができる。 で表現しているから、偶数奇数の判断に影響を及ぼさない。よって、ダブルクオーテーションが奇数個の行が出 が含まれているために分割されてしまっているのだ。値としてのダブルクオーテーションは元々の 1 つを 2 個 ションがその行単独では閉じてないということを意味している。つまり本来は同一行なのだが、値としての改行 最初の 2 行はダブルクオーテーションの数がそれぞれ奇数個である。これは列を囲っているダブルクオーテー
レシピ 1.6 改行無し終端テキストを扱う レシピ 1.6 改行無し終端テキストを扱う 問題 9 標準入力から与えられるテキストデータで、見出し行 ( インデント無しで大文字 1 単語だけの行と定義 ) を除 去するフィルターを作りたい。だた、それ以外の加工をされると困る。例えば入力テキストデータの最後が改 行で終わっていない場合は、出力テキストデータも最終行は改行無しのままであってもらいたい。 つまり、次のような動きをする FILTER. sh を作りたい。 $ printf 'PROLOGUE%nA 10 Ⅱ g time ago... ー FILTER. sh ロ A long time ago ・ . ' ー F 工 LTER. sh ロ $ printf ' PROLOGUE*nA long t ime ago ・ ←元データが改行無し終端なので改行せずにプロンプトが表示されている A 10 Ⅱ g time ago ・ 回答 grep -V'ACA-Z]*{I,*}$' というフィルターを作り、これを通せば見出し行を除去することはできる。だが、 最終行が改行で終わっていなければ最後に改行コードを付けてしまうので一工夫する必要がある。 どのように一工夫すればよいか、答えはこうだ。まず目的のフィルターを通す前、入力データの最後に改行 コードを 1 つ付加する。そしてフィルターを通し、その後でデータの改行コードを取ってしまえばいい。結局こ の問題文の目的を果たすには、具体的には次のようなコードを書けばよい。 ・データの末端に余分な改行を付けないフィルター printf ' PROLOGUE%nA 10 Ⅱ g time ago ・ (cat echO) awk ' BEGIN{ getline line; print line ; dlm=sprintf ( " \ Ⅱ " ) ; while (getline 1 土Ⅱ e ) { print dlm , line ; ただし、目的のフィルターが sed コマンドを使ったものであった場合は注意が必要。 BSD 版の sed コマンド は最終行が改行終わりでなければ改行コードを付加するが、 GNU 版の sed コマンドは改行コードを付加しない。 この違いを吸収するため、 sed コマンドだった場合には、最後の AWK コマンドの前に "grep ~ ' 等、改行を付 けるコマンドを挿む必要がある。
2 第 1 章 ちょっとうれしいレシピ レシピ 1.2 sed による改行文字への置換を、綺麗に書く sed コマンドで任意の文字列 ( 説明のため "*n ”とする ) を改行コードに置換したい場合、 GNU 版でない sed 問題 でも通用するように書くには と書かねばならない。 回答 しかしこれは綺麗な書き方ではないので何とかしたい。 シェル変数に改行コードを代入しておき、置換後の文字列をしている場所にそのシェル変数を書けば綺麗に書 ける。ただし、末尾に改行コードのある文字列をシェル変数に代入するには一工夫必要だ。 まとめると、次のように書ける。 ■改行コードへの置換を綺麗に書く sed において改行コードを意味する文字列の入ったシェル変数を作っておく 解説 LF=$ (printf '%*n— ' ) # ーーー標準入力テキストデータに含まれる " \ n ”を改行コードに置換する一 * 1 GNU 版 sed なら独自拡張により置換後の文字列指定にも "*n ”という記述が使えるが、それは sed 全般に通用する話ではない。 これを綺麗に書く方法は「回答」で示したとおり、“ \ ”と改行コード (<0x0A>) の入ったシェル変数を作り、 wc ー 1 done cat "$file" while read file; do find /TARGET/DIR ー インデントしている場合は汚いったらありやしない wc ー 1 cat textdata. txt ー インデントしてない場合はまだマシ している場合の見た目の汚さは最悪だ。 できることはできるのだが記述が少々汚くなってしまう * 1 。インデントしていない場合はまだしも、インデント 例えば入力テキストに含まれる "*n ”という文字列を本当の改行に置換したいという場合、 sed でもちゃんと
56 仕様に基づき、実装する ■ 1 ) 値としての改行の処理 第 3 章 POSIX 原理主義テクニック この性質がわかれば正規化作業も見通しがつく。 AWK で 1 行ずつダブルクオー テーション文字数を数え、奇数個の行が出てきたら、次にまた奇数個の行が出てくるまで行を結合していけばい い。ただし、単純に結合するとそこに値としての改行文字があったことがわからなくなってしまうので通常のテ キストファイルには用いられないコントロールコード ( く 0x0F > を選んだ ) を挿んだうえで結合していく。 ■ 2 ) 値としてのダブルクオーテーションの処理次は、値としてのカンマに反応しないように気をつけながら、 行の中に含まれる各列を単独の行へと分解していく。基本的には行の中にカンマが出現するたびに改行に置換し ていけばいいのだが、 2 つの点に気をつけなければならない。 1 つは値としてのカンマを無視することであるが、 これは先にダブルクオーテーションが出現していた場合は次のダブルクオーテーションが出現するまでに存在す るカンマを無視するように正規表現置換をすればいい。ただ、値としてのダブルクオーテーションがあると失敗 してしまうので、実は手順 1 ) の前でそれ ( ダブルクオーテーション文字の 2 連続 ) を別のコントロールコード ( <OxOE> とした ) にエスケープしておくのだ。そしてもうーっ気をつけなければならないのが、元々の改行と 列区切りカンマを置換して作った改行を区別できるようにしなければならないということだ。その目的で、元々 の改行が出現した時点で更に第 3 のコントロールコード ( レコード区切りを意味するく 0x1E > を選んだ ) を挿 むようにした。 ・ 3 ) 列と行の数を数えて番号を付けるあとは、改行の数を数えれば作業は大方終了だ。改行が来るたびに列番 号を 1 増やしてやればよいが、元々の改行の印として付けた第 3 のコントロールコードが来た時点で 1 に戻し てやる。最後は、そうやって出力したコードに残っている第 2 のコントロールコードを戻す。これは何だったか というと値としてのダブルクオーテーションであった。最初、変換した時点では 2 個のダブルクオーテーション で表現されていたが、元々は 1 個のダブルクオーテーションを意味していたのだから 1 個に戻してやればよい。 概要は掴めただろうか。掴めなかったとしても、とにかく POSIX の範囲のコマンドだけでできるんだという ことがわかれば十分だ。 参照 →レシピ 3.6 (JSON ファイルを読み込む ) →レシピ 3.7 (XML 、 HTML ファイルを読み込む ) レシピ 3.6 」 SON ファイルを読み込む 問題 WebAPI を叩いて得られた JSON ファイルの任意の箇所の値を読み出したい。 ファイルの比ではなく構造が複雑だ。それでもできるのか ? しかし、先程解説された CSV
レシピ 1.3 grep に対する fgrep のような素直な sed それを置換後の文字列として指定してやればよい。 改行で終わる文字列の入ったシェル変数を作る そのシェル変数を作る際、次のように即値で記述することもできる。 3 しかしこれでは結局、ソースコードを綺麗に書くという目的の達成はできていない。そこで、 printf コマンド を使って“ \ ”と改行コード (<0xOA>) の文字列を動的に生成し、それをシェル変数に代入するのだが、直接代 入しようとすると失敗する。それは、コマンドの実行結果を返す“ $ ( ~ ) ”あるいは“ ( 果の最後に改行があるとそれを取り除いてしまうからだ。 ' という句が、実行結 取り除かれないようにするには、改行コードの後ろにとりあえずそれ以外の文字の付けた文字列を生成して代 入してしまう。そして、シェル変数のトリミング機能 ( この場合は右トリミングの“ % ” ) を使い、先程付けてい た文字列を取り除いて再代入する。この時は“ $ ( ~ ) ”句を使っていないから、文字列の末尾が改行であっても 問題無く代入できるのである。 シェル変数を sed に混ぜて使う場合の注意点 こで作ったシェル変数を用いて今回の置換処理を記述する時、 と書かないように注意。 $ LF をダブルクオーテーションで囲まなければならない。ダブルクオーテーションで囲 まれなかったシェル変数は、その中に半角スペースやタブ、改行コードがあるとそこで分割された複数の引数が あるものと解釈されてしまう。つまり、 "s/**n/* ”と“ / g ' が別々の引数であると解釈され、エラーになってし まうからだ。 これは sed に限った話ではないので、コマンド引数をシェル変数と組み合わせて生成する時は常に注意する レシピ 1.3 grep に対する fgrep のような素直な sed 問題 シェル変数に入っている文字列に置換されるマクロ文字列を定義し、それをテキストファイルの中に配置し たい。 sed コマンドを使おうと思うのだが、シェル変数にはどんな文字が入っているのかわからない。つまり sed の正規表現で使うメタ文字が入っている可能性もあるので、単純にはいかない。 回答 sed がメタ文字として解釈しうる文字を予め工スケープしてから sed に掛ける。具体的には次のコードを通す ことで安全にそれができる。置換前の文字列 ( マクロなど ) が入っているシェル変数を $ fr 、置換後の文字列が 入っているシェル変数を $to とすると、
レシピ 5.26 sed コマンド 補足 . 親プロセス ID(PPID) 115 Linux では、親プロセス ID が 0 になるのは PID が 1 の "init ”だけだ。しかし、廐 eeBSD 等では他の様々な システムプロセスの場合はそれ以外のプロセスの親も 0 になる場合がある。これは、 ps コマンドの違いという よりカーネルの違いであるが、互換性のあるプログラムを書くときには注意すべきところだ。 wo て ld ! $ He110 , $ printf ' He110 ,%nworld! ' ー sed ' ' ロ EGNU 版 sed の場合 wo て ld ! He110 , $ printf ' He110 ,*nworld! ' ー sed ' ' ロ •BSD 版 sed の場合 試しに printf ' He110 ,*nworld! ' 最終行が改行コードでないテキストの扱い sed にもまた AWK 同様に、複数の注意すべき点がある。 レシピ 5.26 sed コマンド ー sed , , というコードを実行してみてもらいたい。 め、 AWK や grep 等、最終行に改行コードがなければ挿入されてしまうコマンドでの対処法を別のレシピとし 動くことを目標にするなら BSD 版のような実装の sed とて無視するわけにはいかない。このような sed をはじ 純粋なフィルターとして振る舞ってもらいたい場合には GNU 版の方が理想的ではあるが、すべての環境で GNU 版はしないようだ。 と、このように挙動が異なる。最終行が改行コードで終わっていない場合、 BSD 版は改行を自動的に挿入し、 て記した。→レシピ 1.6 ( 改行無し終端テキストを扱う ) 参照 使用可能なコマンド・メタ文字 これも、 GNU 版は独自拡張されているので注意。 sed の中で使えるコマンドに関して迷ったら、 POSIX の sed コマンド * 23 を見る。 * 23 http : //pubs ・ opengroup ・ org/on1inepubs/9699919799/uti1ities/sed.れtml また、 sed で使用可能な正
レシピ 3.2 Apache の combined 形式ログを扱いやすくする 47 このシェルスクリプトを通した後、日時フィールドが欲しければ awk '{print $ 4 } , 、同様に HTTP リクエ ストパラメーターフィールドなら awk ' {print $ 5 } ) 、 User-Agent フィールドなら awk ' {print $ 9 } ' をパ イプ越しに書き足せばよいわけだ。 解説 ご承知のとおり、 Apache で一般的に使われている combined という形式のログはこんな内容になっている。 [ 17 / Apr / 2014 : 11 : 22 : 33 + 0900 ] "GET /index. html HTTP/I. 1 ” 200 43206 "https : //www.g 192. 168.0. 1 00g1e. CO ・ jp/" " MOZ 土 11a / 5.0 (Windows NT 6.1 ; WOW64 ) App1eWebKit/537.36 (KHTML' like GeckO) Chrome / 34.0.1847.116 Safari/537.36" User-Agent フィールド ("mozi11a/5.0 (Windows NT 6.1 思って、 AWK で抽出しようとしても $ awk ' {print $ 12 } 'httpd—access.10g ロ ” Mozi11a / 5 . 0 となってしまって、全然使い物にならない。 saf 矼 i / 537.36 " の部分 ) が欲しいと しかし、そこは我らが UNIX0 シェルスクリプトとパイプと標準コマンドである sed と tr さえあればお手の ものだ。他言語に走る必要など全く無い。 「回答」で示したシェルスクリプトに掛けてみるとご覧のとおりだ。 $ cat httpd-access ・ 10g ー apacomb-norm. sh ロ [ 17 / Apr / 2014 : 11 : 22 : 33 ー + 0900 ] "GET-/index. htm1—HTTP/1.1 " 200 43206 192 . 168.0. 1 ttps : //www.google ・ co ・ jp/'l "Mozi11a/5.0—(Windows—NT—6.1 ; -WOW64)—AppleWebKit/537.36 ー ( K HTML,_Iike—Gecko) _Chrome/34.0.1847.116—Safari/537.36 " 各々の sed と tr は何をやってるのか ? 1 つ 1 つ説明していこう。 ( 加工の都合により、途中で一時的に改行を挿むので ) 元の改行を別の文字く 0x1E > で退避させて •sed # 4 •sed # 3 単独の行にする。 •sed # 2 おく。 •sed # 1 プラケットで囲まれている区間 [ ~ ] も同様に、前後に改行を挿んで、この区間を単独の行にする。 " があったら、その前後に改行を挿み、その区間を ダブルクオーテーションで囲まれている区間 " ダブルクオーテーション、またはプラケットで始まる行は、先程行を独立させた区間なので、これら •tr#2 退避させていた元々の改行を復活させる。 •tr#l 改行を全部取り除く。 の行にある空白をそうでない文字列に置換する。
回答 は後回しにするが、そうやって制作した CSV パーサー いる。 54 レシピ 3.5 CSV ファイルを読み込む 問題 sed や AWK を駆使すれば POSIX の範囲でパーサー ( 解析プログラム ) の作成が可能である。原理の解説 第 3 章 POSIX 原理主義テクニック Excel から工クスポートした CSV ファイルの任意の行の任意の列を読み出したい。しかし実際読み出すと なったら、列区切りとしてカンマと値としてのカンマを区別しなければいけなかったり、行区切りとしての改 行と値としての改行を区別しなければいけなかったり、さらに値としてのカンマや改行を区別するためのダブ ルクオーテーション記号を意識しなければいけなかったり、大変だ。 parsrc. sh ”があるので、それをダウンロード * 3 して用 例えば、次のような CSV ファイル (sample. csv) があったとする。 cc " , d d これを次のようにして parsrc. sh に掛けると、第 1 列 : 元の値のあった行番号、第 2 列 : 元の値のあった列番号、 第 3 列 : 値、という 3 つの列から構成されるテキストデータに変換される。 ・ /parsrc. sh sample. csv ロ 1 1 1 1 2 1 2 b"bb 1 f , f 4 d d 3 c*ncc ←値としての改行は "*n ”に変換される ( オプションで変更可能 ) よって、後ろにパイプ越しにコマンドを繋げば「任意の行の任意の列の値を取得」もできるし、 ついて指定した列の値を抽出」などということもできる。 * 3 https://github. com/SheIIShoccar-jpn/Parsrs/b10b/master/parsrc.sh にアクセスし、そこにあるソースコードをコピー & ペーストしてもよいし、あるいは "RAW ”と書かれているリンク先を「名前を付けて保存」してもよい。 「全ての行に
IP アドレスを調べる (IPv6 も ) STR_I く LF> STR_ 2 く LF> STR-n く LF 有ったり無かったり > く LF> ↓ ( 各行末の改行コードが次行の行頭に移動したように扱う ) STR_ 1 レシピ 1.7 レシピ 1.7 旧アドレスを調べる ( IPv6 も ) ちょっと不思議な気もするが、そういうことだ。 STR-n く LF 有ったり無かったり > STR_2 く LF > STR_ 1 く LF> Ⅱ ( これはつまり・・ く LF>STR-n く LF 有ったり無かったり > く LF> STR_ 2 STR_ 1 ↓ ( 最終行の改行をトル ) く LF> く LF>STR-n く LF 有ったり無かったり > く LF>STR_2 11 問題 現在自分が動いているホストの IP アドレスを全て抜き出し、 ただし、知りたいのはグローバル IP アドレスだけ。 回答 ファイルに書き出したい。 一部の L ⅲ ux では古いコマンド扱いされるようになった ifc 。Ⅱ fig コマンド新だが、 UNIX 全体の互換性を考え ればまだまだ不可欠。とりあえず、下記のコードをコピペすれば大抵の環境では動くだろう。 * 5 中には、後から追加インストールしないと存在しない Linux ディストリビューションもある。