最終更新日 …
$Date: 2003-05-18 19:23:34+09 $
フィルタとは,入力したテキストを加工して出力するプログラム一般を指す.
unix には単純な機能を持つフィルタが多い.
これらをパイプでつなげることで,
複雑かつ大量のテキスト加工が,簡単にかつ一度にできるようになっている.
これは他の OS にはあまり見られない機能であり(VMS などにはそうした機能があるそうだが),
unix の大きな特徴である.
コンピュータ上の情報の多くはテキストという形で保存されているため,
複雑で大量テキスト加工が簡単なコマンドの組み合わせで実現できる unix
の情報処理能力は他の OS に比べて高いということになる.
それもあって,フィルタを使いこなすということは unix を最も unix らしく使うことといえよう.
まず,単純な機能,事実上一つの機能しか持たないフィルタ(プログラム)を紹介しよう.
ごく簡単な処理はこれらのフィルタを組み合わせることで大体できるはずだ.
いいかえると,これらのフィルタの組み合わせで出来ることを,
一生懸命手動で行なったり,それ専用のプログラムを新たに用意したりするのは愚かな行為である.
# この警告を理系世界では「新たに車輪を発明するな」などと表現する.
既に存在するものを新たに作る労力をかけるならば,その労力をより意味のある行為に当てよ,という意味である.
これら単機能なフィルタは長い間使い込まれたプログラムが多く,
その分鍛え抜かれているといえる.
実際,メモリや cpu への負担は小さく,頑健で素早く動作し,バグも少ない.
安心してガンガン使おう.
rsh
(後の授業で解説する)と組み合わせて
「ネットワークを経由してファイルをコピーする」
などがある.
ls -lg | cat -n | less
などとすると,出力結果に行番号がつくので,情報を把握しやすくなる…かもしれない.
ls -lg | rsh host 'cat > dummy.txt'
などとすると,出力結果をネットワークを越えたマシン
"host"
上の
"dummy.txt"
というファイルに書込むことができる.
cat
と似たような動作だが,
出力先が「標準出力」と「ファイル」の両方にコピーされる,という点が特徴である.
より詳しくは
前回の授業の tee 解説部分
を見よ.
ls -lg | cat -n | tee dummy.txt | less
などとすると,出力結果に行番号がつくのを見られると同時にファイル
"dummy.txt"
に結果が書込まれる.
egrep
は,パターンに「拡張正規表現」が使える(参照先 → 正規表現).
fgrep
は,パターンを「単なる文字列」として扱う.
しかし,fgrep
を使えば検索が早いかというと必ずしもそうではない(^-^).
(fgrep の f は fast ではなくて fixed- の頭文字である)
zgrep
は圧縮したファイルを解凍してから,その中身を検索する.
ただし,zlib とよばれるライブラリを使ってコンパイルされている場
合のみ使える(つまり,あんまり当てにするなということ(^-^)).
ps -axu | grep xemacs
とすれば(目で一生懸命探すという)苦労をせずに見ることができる.
grep xemacs
も検索されてしまうのがイヤだという場合は,
ps -axu | grep '[0-9] xemacs'
ps -axu | awk '{print $2,$11}' | grep xemacs
sort
は比較的よく使うフィルタなので,そのオプションをマニュアルで調べ,覚えておくぐらいが良いだろう.
ps -axu | sort +10
とやればプロセスのリストを「プログラムの名前順に」,
ps -axu | sort -r +10
とやればプロセスのリストを「プログラムの名前の逆順に」
みることができる.
ls -lg | sort -nr +4
とすれば,ファイルを「サイズの大きな順に」並べることができる.
HDD の容量が残り少なくて,無駄なファイルがないかな〜 と調べるときなどに有効だろう.
sort
フィルタを通してから
uniq
フィルタを通すと,全体の重複を除去できるので,こうして使うことも多い.
(ls /usr/bin; ls /usr/local/bin) | sort | uniq
(ls /usr/bin; ls /usr/local/bin) | sort | uniq -d
ls -lg | tr a-z A-Z
とすると,
ls -lg の結果を,a → A, b → B, c → C … z → Z と変換して出力する.
つまり,全て大文字に置換する.
tr -cd [:print:]
とすると,印刷できない文字を全て入力から消去する.
tr -s '\n'
とすると,入力中の改行の繰り返しを一つにまとめる.
つまり,空行を消去する.
cat dummy.txt | fold -w 8 | less
ls -lg | pr | less
などとしてみると簡単にその意味が分かる.
ls README* | wc -w
としてみると,ディレクトリに README という文字列で始まる名前を持つファイルがいくつあるかがわかる.
nkf -e
… 入力テキストを euc に変換して出力する.
nkf -j
… 入力テキストを jis に変換して出力する.
nkf -s
… 入力テキストを shift-jis に変換して出力する.
nkf -e < dummy-win.txt | less
ls -lg | compress > dummy.txt.Z
とすると,
ls -lg の結果が圧縮されて dummy.txt.Z という名前のファイルになる.
ls -lg > dummy.txt
もやってみたあと,
ls -lg dummy.txt*
などとしてファイルサイズがどれくらい小さくなっているか(圧縮されているか)
実際に見てみるとよい.
ls -lg > dummy.txt
としてから,
compress dummy.txt
とするのが普通だ.
ここでは「フィルタ」としての使い方を強調するため,こうした書き方をしている.
ls -lg | gzip > dummy.txt.gz
とすると,
ls -lg の結果が圧縮されて dummy.txt.gz という名前のファイルになる.
ls -lg > dummy.txt
としてから,
gzip dummy.txt
とするのが普通だ.
ここでは「フィルタ」としての使い方を強調するため,こうした書き方をしている.
tar cf - -C ディレクトリA . | tar xpf - -C ディレクトリB
とすると,ディレクトリA の中身をそのままディレクトリ B へコピーできる.
パイプの間に
rsh
を挟めばネットワークを越えてディレクトリの構造をコピーできる.
ps -axu | awk '{print $2, $11}'
ps -axu | cut -f 2,11 -d " "
実習
上に示した実例を実際に行い,何が起きているのかよく理解せよ.
正規表現とは,文字列の集合を表すために考えられたルールの一つである.
人間が文字列処理を行なうとき,指定したい文字列を人間に伝えるのは簡単だが,
機械に対して正確に指定するのが難しい,ということは良くあることだ.
例えば,re で始まって,tion で終わる単語を文書中から探してくれ,
というのをコンピュータに伝えるにはどうしたらいいだろうか?
こうした人間の要望を正確かつ厳密に表現する方法として,正規表現は存在する.
unix では当たり前のように使われるルールであるので,使いこなせるようになっておくべし.
ちなみに,正規表現そのもののマニュアルは,
man 7 regex
man re_format
とすることで読むことができる.
基本正規表現 | 拡張正規表現 | 意味 |
---|---|---|
通常文字 |
メタキャラクタでない文字.
その文字自身を表す.
メタキャラクタとは, 基本正規表現では \ ^ $ . [ ] * の7文字を, 拡張正規表現では \ ^ $ . [ ] * + ? { } ( ) | の14文字をいう. |
|
\m | メタキャラクタ m の意味を打消し,通常文字として扱う(エスケープという). | |
^ | 行頭を表す. | |
$ | 行末を表す. | |
. (ピリオド) | 任意の一文字を表す. | |
[ ] |
[ ] で囲まれた文字列中のどれか一文字を表す.
[ ] 中では特別に "-" のみがメタキャラクタとなり, 他のメタキャラクタは通常文字として扱われる. "-" を通常キャラクタとして扱うには, "---" (ハイフンを三つ繋げる) と書けばよい. |
|
[ c1 - c2 ] | 文字 c1 から c2 までの範囲の文字中のどれか一文字を表す. | |
[^ ] | [^ ] で囲まれた文字列中に「含まれない」一文字を表す. | |
* | 直前の正規表現の 0 回以上の繰り返しを表す. | |
+ | 直前の正規表現の 1 回以上の繰り返しを表す. | |
? | 直前の正規表現が 0 回か 1回現れることを表す. | |
\{ m \} | { m } | 直前の正規表現の m 回の繰り返しを表す. |
\{ m, \} | { m, } | 直前の正規表現の m 回以上の繰り返しを表す. |
\{ m, n \} | { m, n } | 直前の正規表現の m 回以上 n 回以下の繰り返しを表す. |
\( \) | ( ) | 囲まれた部分をグループ化する. |
\N | N 番目のグループ化された正規表現が合致した結果(N= 1,2,..9). | |
| | 直前と直後の正規表現の「どちらか」を表す | |
\< | 単語の先頭を表す. GNU によって作成されたソフトで実装されている(GNU 拡張). | |
\> | 単語の末尾を表す. GNU によって作成されたソフトで実装されている(GNU 拡張). | |
\b | 単語の先頭か末尾を表す. GNU によって作成されたソフトで実装されている(GNU 拡張). | |
\B | 単語の(先頭か末尾)以外を表す. GNU によって作成されたソフトで実装されている(GNU 拡張). |
emacs で正規表現を用いてみる
正規表現を用いる例を紹介しておこう.
例えば,この web page をファイル 05.html として保存してあるとする.
そして,このファイルを(web browser を用いて)web page としてではなく,普通にテキストファイルとして読みたい,
と思ったとしよう.
すると,xhtml のタグ
(< ... > というやつ)
は邪魔だ… ということでこれを除去することにする.
この作業を例えば emacs でやるには次のようにすればよい.
M-x replace-regexp
とする.
Replace regexp:
と出て,入力を要求される.
<[^>]*>
と入力して Return を押す.
Replace regexp <[^>]*> with:
と出て,入力を要求される.
実習
単機能フィルタでは少々荷が重い複雑なフィルタ処理を行なうには,
そのままでは,
シェルスクリプトを作成するか,
通常言語でプログラムを組むなどの行為が必要となる.
しかし,シェルスクリプトは制限が強すぎて柔軟な処理は難しいし,
通常言語でフィルタの内容をプログラムするのは無駄が多い.
そこで,フィルタプログラム自身が複雑なフィルタ処理をプログラムできれば,
フィルタの便利な機能を生かしつつ,無駄無く複雑な処理が柔軟にできるというものだ.
これが「プログラミング可能な」フィルタの存在意義である.
こうしたプログラミング可能なフィルタプログラムとしては,
sed,
awk,
perl,
ruby
等が有名である.
# これは単なるフィルタじゃない…と文句が出そうなものも含まれているが(^-^)
awk, perl, ruby 等はプログラミング言語としてとらえないと全く理解できない恐れがあるので,
それらについての解説は後の講義で別に時間を設ける予定である.
そこで,本講義では,cui ユーザとして必須なツールである sed についてまず簡単に解説する.
先に注意しておくが,sed や awk でできることは perl などでも可能である.
しかし,sed や awk の方が,
シンプルで機能が少ない分だけ理解しやすくかつコンピュータへの負荷も小さい(つまり速い)
という利点があるので,
これらで簡単にできることはこれらで行なう,というのが正しい姿だろう.
sed は本来 Strem EDitor と呼ばれるエディタであり,実は非常に複雑なことが出来る.
ただし,出来るには出来るのだが,
おそろしく単純な機能だけを用いて複雑なことを行なうことになるので,
分かりにくい上に労力だけ浪費することがほとんどだろう.
よって,
よほどの物好きでない限り sed をエディタとして用いることはないと言ってよい.
# 頭の体操や罰ゲームにはいいかもしれない.
現在では sed は,
高速 かつ 正規表現の使える
「文字列置換ツール」
として使われるのが主流である,といってよい.
そこで,ここでもその路線に従った解説を行なう.
-n
は,後述するコマンド
p
と組み合わせて用いるものである.
処理対象となる行の指定 { コマンド コマンド コマンド コマンド }という構造をしている.
コマンド コマンド コマンド コマンドとコマンドだけを書き連ねてもよい.
-n
を使用した場合は,
p
コマンドの時だけ出力する.
p
コマンドを使用するときは混乱しないように
-n
オプションを使用するのだ,と覚えておけば大丈夫だろう.
行番号
… 10 と書いたら 10行目のこと.
/正規表現/
… その正規表現を「含む」行(複数ありうる).
$
… 最終行.
10, /win/
のように二つの指定を ,(カンマ) で繋げて書いた場合には,
10行目から win を含む行まで
という範囲指定になる.
コマンド | 意味 | ||||||||
---|---|---|---|---|---|---|---|---|---|
s/置換元パターン/置換新パターン/フラグ |
置換元パターン(正規表現可) に合致する文字列を
置換新パターン で置き換える.
置換新パターン中には,特別に & というメタキャラクタも使える. これは置換元パターンに合致した文字列そのもの,を表す.
|
||||||||
p |
その行を表示する.
-n
オプションとの関係は記した通り.
|
||||||||
d | その行を削除する. | ||||||||
q | sed そのものを終了する. |
sed の簡単な実習
さて,慣れてないと上のような抽象的な説明では良く分からないだろう.
よって,簡単な例をやってみて実感するのがよいだろう.
いくつか例を挙げてみるので,自分で体感してみよ.
実行例
ls -lg | sed -e 's/emacs/ORAORAORAORA/g' | less
さあ,何が起こっているか分かるだろうか.
さらに,
ls -lg | sed -n -e 's/emacs/ORAORAORAORA/gp' | less
とすると,より分かりやすいだろう.
また,友人の名前とあだ名を使って次のようなスクリプト
s/友人A/いい奴/g s/友人B/ちょっといい奴/g s/友人C/金返せ/g
を書いたとしよう.
このスクリプトを
"dummy.sed"
という名前のファイルで保存したとしよう.
すると,例えば日記を
"nikki.txt"
という名前のファイルに書いていたとしたら,
cat nikki.txt | sed -f dummy.sed | less
とすれば,友人たちの名前が書き変わった日記を読むことができる(^-^).
もっと細かくやれば,標準語で書かれた文章を関西弁に直すことなどもできるだろう.
また,正規表現を使ったやや複雑な例としては,多くのファイルの名前を自動的に変更するというのもできる.
例えば,*.text というファイルをすべて *.txt というファイル名に変更したいとしよう.
すると,次のようにすればよいのである.
ls *.text | sed -n -e 's/\(.*\).text$/mv & \1.txt/gp'
例えば a.text, b.text というファイルがあるとすれば,
これを実行すると
mv a.text a.txt mv b.text b.txt
という結果が得られる.
あとはこれをシェルスクリプトにしてもよいし,シェルそのものに渡しても良い.
つまり,
ls *.text | sed -n -e 's/\(.*\).text$/mv & \1.txt/gp' | tcsh
などとするもよし,ということだ.
これらの結果からも想像できるだろうが,
sed を使えば大量の文字列置換が自動的にかつ高速にできるようになるのである.
以下に示された課題について
AppliedMath-Report-05
という題名をつけて e-mail にて教官宛にレポートとして提出せよ(教官のメールアドレスは授業中に口頭で伝える).
なお,レポートを e-mail の代わりに TeX で作成した書面にて提出してもよい.
課題内容
2003.05.15
というような形式で記述されている.
15/May/2003
という形式に修正したいが,どうすればよいか.