[ UNIX 入門 第12回 『UNIX (5) --- シンボルを操作する』 (私が1998年に私的に行ったものの資料) ] を HTML 化したものです。

シンボルを操作せよ!!
Manipulate the Symbol!

「UNIX を使うと実際にどう役に立つのか、どう使えるのかについて言わないと 『UNIX 入門』の意味がない」という声が突然湧き上がったのを受けて 急遽作成した資料です。けど「ふだん使っている環境の延長線上でそのまま」 ということを意識しすぎて、最後の C-Shell スクリプトのところで爆死 しちゃっているような印象を受けてしまいます (^o^;

あと諸般の事情により駆け足で進んでいますので、わかりにくさ倍増 (^_^;;


もくじ

  1. 1. 状況設定
  2. 2. grep を使う (1) --- とりあえず使ってみよう
  3. 3. grep を使う (2) --- もっと条件を絞ろう
  4. 4. sed を使う --- もっと見やすくしよう
    1. 4.1 非対話型エディタ sed とは?
    2. 4.2 sed を使ってみよう
      1. hack を含むファイル名を抽出
      2. ファイルの内容の出力
  5. 5. 「見やすさ」への こだわり --- プログラミングのはじまり
    1. 5.1 less で見やすく
    2. 5.2 LaTeX で見やすく
    3. 5.3 HTML で見やすく
  6. 6. まとめ
  7. 演習問題


シンボル操作をおこなってみよう。 「電子テキストをハックする!」

1. 状況設定

以下のディレクトリに、フリーの電子辞書*1である Roget's Thesaurus (Thesaurus-1911, PROJECT GUTENBERG-tm etext) の内容をカテゴリ(同義語)ごとに分割したファイルがある。
~ta/misc/nyuumon-sample
対象となるファイル数は全部で 989 個である。
% cd ~ta/misc/nyuumon-sample
% ls [0-9]* | wc
989 989 3851
本節では、これらのファイルから `hack' という単語を検索し、 `hack' という単語の語義(同義語)について調査をおこないたいんだけど、 どうしたらいいの? という問題に直面した事態を想定し、 このような目的を達成するための補助として、 計算機をチョロっと使ってどの程度のことができるか、 について述べていく。

2. grep を使う (1) --- とりあえず使ってみよう

grep とは、いまは人文系では必須となっているツールで :-) 、 以下のようにして使用する。
% grep '*pattern*' files..
今回の我々の目的は `hack' という単語を探すことなので、 以下のように指定してやるとよい。
% grep 'hack' *
実行すると以下のようになる。(出力の一部は省略した) それぞれの行は [ファイル名:パターンにマッチした行の中身] の組に なっている。
% grep 'hack' *
189:dwelling[obs3], lacuspile dwelling[obs3]; log cabin, log house; shack,
192: lubberly, hulky, unwieldy, lumpish, gaunt, spanking, whacking,
201: retrench, cut short, obtruncate[obs3]; scrimp, cut, chop up, hack,
253:clivers[obs3], goose, grass, hairif[obs3], hariff, flax comb, hackle,
***(snip)***
752:hackamore [obs3][U.S.], headstall, jaquima [obs3][U.S.], lines, ribbons.
975:blacksnake[obs3], bullwhack [obs3][U.S.], chicote[obs3], kurbash[obs3],
grep: src: Is a directory

3. grep を使う (2) --- もっと条件を絞ろう

上記のとおりにすれば用は足りたかのように思われる。 だが、出力の最初の行を見てみてみると hack とは関係がない shack という文字列が `hack' というパターンを含んでいた ために出力されていることがわかり、 単に「`hack' を含む行を持ってこい」としただけではゴミが多すぎて、 さして嬉しいわけではないことがわかる。 それゆえ、もっと細かく *pattern* が指定できると便利である。

そこでよく利用されているのが「正規表現」というものである。

. なにか一文字
*これだけでは意味がなく、直前のパターン 0 個以上
+これだけでは意味がなく、直前のパターン 1 個以上
?これだけでは意味がなく、直前のパターン 0 個か 1 個
^パターンの最初にあった場合、「文字列の先頭」
$パターンの末尾にあった場合、「文字列の末尾」
[ ][] に囲まれた文字のうち、どれか (a-z で「aからz」)
[^ ][^] に囲まれた文字以外の文字のうち、どれか
( | )() に囲まれ、かつ | によって区切られたパターンのうち、どれか
\上記の各文字を普通の文字として使いたいときに使う (例 \.) \\
[ 表1: 基本的な正規表現 ]

基本的には普通の文字列であるが、表1の ような特殊な指定ができる。 パターンマッチの例を以下に示す。

aaa aaa ; a+ ; b* ; ^a ; a$ ; a ; .
abc a+- ; a* ; ^a ; ab ; ^.bc
Emacs [Ee] ; [Ee]macs ; a+ ; z*
screen scre+n ; [Ee] ; ^(scr|eil)een ; e+n$
beggiest ^[^c-z] ; [^b] ; big(ger|gest)?
さて。今回の例の場合、元となるデータの書式を見てみると.. だいたい、このように書かれていることがわかるので、パターンとしては 以下のような感じで書けばよさそうだ、という見当がつく。 そこで、さっそく実行してみよう。すると以下のような結果が得られる。 *2
% grep '[^a-z]hack,' *
201: retrench, cut short, obtruncate[obs3]; scrimp, cut, chop up, hack,
271:jument[obs3], pony, filly, colt, foal, barb, roan, jade, hack, bidet, pad,
272:dump cart, hack, hackery[obs3], jigger, kittereen[obs3], mailstate[obs3],
44:craunch[obs3], chop; cut up, rip up; hack, hew, slash; whittle; haggle,
593:journalism; pen, scribbler, the scribbling race; literary hack, Grub-street
737:worker, campaign worker; lobbyist, contributor; party hack, ward heeler;
grep: src: Is a directory

4. sed を使う --- もっと見やすくしよう

しかし上のような結果を見ても、ちっともウレシクない。 なぜなら今回の目的は「 `hack' の語義についての調査をおこなうこと」なの で、 `hack' を含む行だけを切り出してきても意味がないからだ。 今回の場合は、ファイルごとに同義語が分けられているので、本来の目的を 達成するためには「`hack' を含んだファイルを示す」とすればよさそうだ。 そこで、さっそく、やってみよう。 ここでは sed というコマンドを使っている。

4.1 非対話型エディタ sed とは?

sed は指定されたファイル、あるいは標準入力から一行ずつデータを読み込み、 与えられた「スクリプト」に従って処理をおこない、出力をおこなうコマンドである。 コマンドは以下のようにして実行する。
% sed [-n] [-e script] [filenames ..]
sed についての詳しいことは man sed を参照してほしい。 ここでは sed の置換機能だけに話を絞った説明をおこなうので、 sed のコマンドは以下のようなパターンのものだけを使用する。
% sed -e 's/regular-expression/replacement/flags' [filenames ..]
この 's/regular-expression/replacement/flags' の部分を、 たとえば s/aa/ii/ と書くと、sed はすべての入力行の aa を ii に、 一度だけ変換したものを出力してくれる。 たとえば以下のようなテキスト:
aa ii uu ee oo kaa kii kuu kee koo
saa sii suu see soo taa tii tuu tee too
これは以下のように変換されて出力される。
ii ii uu ee oo kaa kii kuu kee koo
sii sii suu see soo taa tii tuu tee too
「すべての aa を ii に変換してしまいたい」というときには、 s/aa/ii/g と、flags のところに g を設定するとよい。

4.2 sed を使ってみよう

話を戻そう。今回の我々の目的を達成するためには、以下の手順が必要 になると思われる。つまり..

hack を含むファイル名を抽出

grep の結果は、以下のように [ファイル名:内容] という書式で 出力されていたことを思い出してほしい。
201: retrench, cut short, obtruncate[obs3]; scrimp, cut, chop up, hack,
よって、sed を使って各行の `:' 以降を切り落としてやれば、 ファイル名の一覧を得ることができる。具体的には s/:.*$// とでも 書けばよさそうだ。
% grep '[^a-z]hack,' * | sed -e 's/:.*$//'
201
271
272
44
593
737

ファイルの内容の出力

各ファイルの内容の出力は、とりあえず cat コマンドを使うことにして おくとして、問題はどうやってファイル名を cat コマンドに渡すか、と いうことになる。

そこでここでは、C-Shell の `` (back quote 表現) と foreach 文を使ってみることにする。

back quote 表現は、要するに「このコマンドの出力結果をここに入れてくれ」 というときに使うものである。

% echo `grep '[^a-z]hack,' * | sed -e 's/:.*$//'`
201 271 272 44 593 737
foreach 文は、以下のように使う。
foreach i ( *.txt )
この場合は「 *.txt にマッチする文字列のそれぞれに対して、 以下のコマンドを実行してくれ。各文字列は $i で参照する」という 意味である。 ここで「以下のコマンド」って?? と疑問に思うかもしれないが、 シェルプロンプトに対して上のような入力をおこなうと、
foreach?
このようなプロンプトが出てくるので、ここで「以下のコマンド」 を入力してやり、最後に end を入力してやるのである。 簡単な例を以下に示す。
% foreach lab ( semi vacia sophia lpsy ling )
foreach? echo $lab is subdomain
foreach? end
semi is subdomain
vacia is subdomain
sophia is subdomain
lpsy is subdomain
ling is subdomain
さて「 foreach 文を使って、hack を含むファイルの内容を表示する」 ためのコマンドは、具体的には以下のように書ける。
% foreach i ( `grep '[^a-z]hack,' * | sed -e 's/:.*$//'` )
foreach? echo "## $i"
foreach? cat $i
foreach? end
ところで、複雑なコマンドを何度も実行しそうな場合、いちいちコマンドラインから たくさんの文字列を入力してやるのは大変なので、 コマンドの内容を「シェルスクリプト」として ファイル化・コマンド化しておくとよい。 たとえば上のコマンドと同じことをおこなうスクリプトは以下のようになる。
#! /bin/csh
foreach i ( `grep '[^a-z]hack,' * | sed -e 's/:.*$//'` )
echo "## $i"
cat $i
end
今後は、このようなスクリプトをどのように書けばよいか、といった 形の説明をしていく。

このスクリプトの名前を ~/un12 としたときの例を以下に示す。

[friedrich:TA 134] % pwd
/home/vacia/ta/misc/nyuumon-sample
% ls -l ~/un12
-rwxr-xr-x 1 ta vacia 93 Jul 10 20:56 /home/vacia/ta/un12*
% ~/un12
grep: src: Is a directory
## 201
#201. Shortness.-- N. shortness &c. adj.; brevity; littleness &c. 193;
a span.
shortening &c. v.; abbreviation, abbreviature[obs3]; abridgment,
concision, retrenchment, curtailment, decurtation|; reduction &c.
(contraction) 195; epitome &c. (compendium) 596.
***(snip)***
%

5. 「見やすさ」への こだわり --- プログラミングのはじまり

前節では「ファイルの中身の出力」に cat コマンドを使っていたが、 これでは決して「見やすい」とは言いにくい。そこで sed を使って、データを見やすくするための工夫を凝らしてみたい。 具体的には、(たいしたことではないが) `hack' という文字列を 太字で表示して、ユーザから発見しやすくする、というものである。

もはやこうなると、簡単ではあるが、立派な「プログラミング」である。

5.1 less で見やすく

less は一部の文字を太字で表示する機能を持っている。
(ある文字)(back space)(ある文字)
このような文字列を発見すると、(ある文字) を太字で 出力するのである。*3具体的には A^HA (とりあえず ^H で back space を表現しておく) とあると A (太字) として表示され、同じように u^Hun^Hni^Hix^Hx とあると unix (太字) として表示される。

よって、less のこの機能を利用すれば、`hack' を含むファイルの 内容を単に cat で出力するよりも、ユーザにわかりやすい出力を することが可能になる。

さて具体的な変更内容であるが、上のスクリプトで cat $i と していた箇所を以下のように変更するだけでいける。 (ここでは back space を ^H として表現している。 backspace は Emacs では C-q C-h あるいは C-q DEL のどちらかで入力可能である。)

sed -e 's/hack/h^Hha^Hac^Hck^Hk/g' $i

5.2 LaTeX で見やすく

また LaTeX を利用することもできる。 LaTeX で出力するために「すべきこと」をまとめると以下のようになる。 まず最初の項目についてである。すでに述べたとおり、入力文の中の hack{\bf hack} に書き換えるのは sed を使えば そんなに難しくはない。
sed -e 's/hack/{\\bf hack}/g'
ただし今回は、今後の拡張のことも考えて以下のように書くことにする。
sed -e 's/hack/{\\bf &}/g'
ちなみに変換後のところの &パターンにマッチした文字列 を示す、特別な表現である。(& という文字を指定するときには \& と書く。) 2 つめの特殊文字の変換は、以下のように書ける。
sed -e 's/[&#<>]/\verb+\&+/g'
これは [&#<>] にマッチする文字列があったら、 それを \verb+&+ 、要するに その文字を \verb+ + で 囲ってしまえ、という意味になる。また空白ではじまる行の前に改行を入れる のは以下のようにして書ける。
sed -e 's/^[ ]/\\
/'
変換後のパターンは \\ ときて改行コードが続いているが、これは 以下の理由による。 これらのコマンドをどうやって組み合わせるか、であるが、最も簡単なのは 以下のように、パイプでそれぞれのコマンドをつなげてやることである。
sed -e 's/hack/{\\bf &}/g' $i | sed -e 's/[&#<>]/\verb+&+/g' | sed -e 's/^[ ]/\\ /'
しかし、いくら何でも sed をパイプで 3 つも繋げるのは格好がよくないので、 ここでは -e コマンドを複数使って、以下のようにしておく。(結果は同じ)
sed -e 's/hack/{\\bf &}/g' -e 's/[&#<>]/\verb+&+/g' -e 's/^[ ]/\\ /' $i
ここまでの結果、スクリプトはこんな感じに書き換えられる。
#! /bin/csh
foreach i ( `grep '[^a-z]hack,' * | sed -e 's/:.*$//'` )
cat <<EOB
\\section{$i}
EOB
sed -e 's/hack/{\\bf &}/g' -e 's/[&#<>]/\verb+&+/g' -e 's/^[ ]/\\ /' $i
end
そして foreach から end までのメイン部分を実行する前後に、LaTeX 特有の 「おまじない」部分を出力する部分を追加してやった結果、以下のような スクリプトができあがる。
#! /bin/csh
cat <<EOB
\\documentstyle{jarticle}
\\begin{document}
EOB

foreach i ( `grep '[^a-z]hack,' * | sed -e 's/:.*$//'` )
cat <<EOB
\\section{$i}
EOB
sed -e 's/hack/{\\bf &}/g' -e 's/[&#<>]/\verb+&+/g' -e 's/^[ ]/\\ /' $i
end

cat <<EOB
\\end{document}
EOB
このスクリプトを実行したときの様子を以下に示す。
% pwd
/home/vacia/ta/misc/nyuumon-sample
% ~/un12 > roget2tex.tex
grep: src: Is a directory
% jlatex roget2tex.tex
This is JTeX, Version 1.52, based on TeX C Version 3.141
(roget2tex.tex
....
(see the transcript file for additional information)
Output written on roget2tex.dvi (5 pages, 26180 bytes).
Transcript written on roget2tex.log.
% xdvi roget2tex.dvi
この結果、以下のような出力が得られる。
latex image
イマイチ物足りないが、今回はこれでよし、としておく (-_-)

5.3 HTML で見やすく

LaTeX 以外にも HTML を使うという方法もある。HTML には ハイパーリンクという特性があるだけでなく、所謂「 CGI スクリプト」 というものに仕立てると http 経由で情報公開が可能になるなど、 いろいろ面白い展開が期待できる。

さて、 HTML で出力する場合も LaTeX で出力する場合と類似した 以下のような「すべきこと」がある。

そこで以下のようなスクリプトを作成した。
#! /bin/csh
cat <<EOB
<html>
<ul>
EOB

foreach i ( `grep '[^a-z]hack,' * | sed -e 's/:.*$//'` )
cat <<EOB
<li><a href="#$i"> #$i </a>
EOB

( echo '\
<hr>\
<a name="'$i'"><h2># '$i'</h2></a>\
' ; sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' \
-e 's/hack/<b>hack<\/b>/g' -e 's/^[ ]/<p>/' $i ) >> /tmp/hoge-hoge
end

cat <<EOB
</ul>
<hr><hr>
`cat /tmp/hoge-hoge ; rm /tmp/hoge-hoge`

EOB
大雑把な流れについては LaTeX のときとほぼ同じである。

6. まとめ

*1
`When we speak of free software, we are referring to freedom, not price.' -- GNU GENERAL PUBLIC LICENSE, Copyright (C) 1989, 1991 Free Software Foundation, Inc.

*2
hackman という、なんとなく関係がありそうな単語が落ちてしま うが、ここでは、なかったことにしておく。

*3
man コマンドの出力の一部が太字になっていたりするのは、そのためである。

演習問題

  1. 実際に grep や sed を使って、単語の検索をしてみなさい。
  2. 5.3 節で出てきたスクリプトの内容を解読せよ。 (わかるところだけでよい)

TA
[Since 1998]