あと諸般の事情により駆け足で進んでいますので、わかりにくさ倍増 (^_^;;
~ta/misc/nyuumon-sample
対象となるファイル数は全部で 989 個である。
本節では、これらのファイルから `hack' という単語を検索し、 `hack' という単語の語義(同義語)について調査をおこないたいんだけど、 どうしたらいいの? という問題に直面した事態を想定し、 このような目的を達成するための補助として、 計算機をチョロっと使ってどの程度のことができるか、 について述べていく。% cd ~ta/misc/nyuumon-sample
% ls [0-9]* | wc
989 989 3851
% 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
`hack'
というパターンを含んでいた
ために出力されていることがわかり、
単に「`hack' を含む行を持ってこい」としただけではゴミが多すぎて、
さして嬉しいわけではないことがわかる。
それゆえ、もっと細かく *pattern*
が指定できると便利である。
そこでよく利用されているのが「正規表現」というものである。
. | なにか一文字 |
* | これだけでは意味がなく、直前のパターン 0 個以上 |
+ | これだけでは意味がなく、直前のパターン 1 個以上 |
? | これだけでは意味がなく、直前のパターン 0 個か 1 個 |
^ | パターンの最初にあった場合、「文字列の先頭」 |
$ | パターンの末尾にあった場合、「文字列の末尾」 |
[ ] | [ と ] に囲まれた文字のうち、どれか (a-z で「aからz」) |
[^ ] | [^ と ] に囲まれた文字以外の文字のうち、どれか |
( | ) | ( と ) に囲まれ、かつ | によって区切られたパターンのうち、どれか |
\ | 上記の各文字を普通の文字として使いたいときに使う (例 \. ) \\
|
基本的には普通の文字列であるが、表1の ような特殊な指定ができる。 パターンマッチの例を以下に示す。
aaa | aaa ; a+ ; b* ; ^a ; a$ ;
a ; . |
abc | a+- ; |
Emacs | [Ee] ; [Ee]macs ; a+ ; z* |
screen | scre+n ; [Ee] ; ^(scr|eil)een ; e+n$ |
beggiest | ^[^c-z] ; [^b] ; big(ger|gest)? |
word,
word[obs ???],
[^a-z]hack,
% 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
sed についての詳しいことは% sed [-n] [-e script] [filenames ..]
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
「すべての aa を ii に変換してしまいたい」というときには、ii ii uu ee oo kaa kii kuu kee koo
sii sii suu see soo taa tii tuu tee too
s/aa/ii/g
と、flags のところに g を設定するとよい。
よって、sed を使って各行の `:' 以降を切り落としてやれば、 ファイル名の一覧を得ることができる。具体的には201: retrench, cut short, obtruncate[obs3]; scrimp, cut, chop up, hack,
s/:.*$//
とでも
書けばよさそうだ。
% grep '[^a-z]hack,' * | sed -e 's/:.*$//'
201
271
272
44
593
737
そこでここでは、C-Shell の ``
(back quote 表現) と
foreach 文を使ってみることにする。
back quote 表現は、要するに「このコマンドの出力結果をここに入れてくれ」 というときに使うものである。
foreach 文は、以下のように使う。% echo `grep '[^a-z]hack,' * | sed -e 's/:.*$//'`
201 271 272 44 593 737
この場合は「 *.txt にマッチする文字列のそれぞれに対して、 以下のコマンドを実行してくれ。各文字列はforeach i ( *.txt )
$i
で参照する」という
意味である。
ここで「以下のコマンド」って?? と疑問に思うかもしれないが、
シェルプロンプトに対して上のような入力をおこなうと、
このようなプロンプトが出てくるので、ここで「以下のコマンド」 を入力してやり、最後に end を入力してやるのである。 簡単な例を以下に示す。foreach?
さて「 foreach 文を使って、hack を含むファイルの内容を表示する」 ためのコマンドは、具体的には以下のように書ける。% 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 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)***
%
もはやこうなると、簡単ではあるが、立派な「プログラミング」である。
(ある文字)(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
{\bf }
とすると太字で出力できるので、
それを利用する。今回の例では hack
を {\bf hack}
と変換してやればよいわけだ。
# & < >
など、そのままでは出力できない文字があるため、
それらの文字を出力できるよう、変換してやる。
また、空白で始まる行を段落の先頭と認識させるため、その行の
前に空行をひとつ挿入してやる。
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/^[ ]/\\
/'
\\
ときて改行コードが続いているが、これは
以下の理由による。
\
が必要
\
を渡すことを示すために C-Shell に対しては
\\
と指定する必要がある
しかし、いくら何でも sed をパイプで 3 つも繋げるのは格好がよくないので、 ここでは -e コマンドを複数使って、以下のようにしておく。(結果は同じ)sed -e 's/hack/{\\bf &}/g' $i | sed -e 's/[&#<>]/\verb+&+/g' | sed -e 's/^[ ]/\\
/'
ここまでの結果、スクリプトはこんな感じに書き換えられる。sed -e 's/hack/{\\bf &}/g' -e 's/[&#<>]/\verb+&+/g' -e 's/^[ ]/\\
/' $i
そして foreach から end までのメイン部分を実行する前後に、LaTeX 特有の 「おまじない」部分を出力する部分を追加してやった結果、以下のような スクリプトができあがる。#! /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
このスクリプトを実行したときの様子を以下に示す。#! /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
(-_-)
さて、 HTML で出力する場合も LaTeX で出力する場合と類似した 以下のような「すべきこと」がある。
<b> </b>
とすると太字で出力できるので、
それを利用する。今回の例では hack
を <b>hack</b>
と変換してやればよいわけだ。
& < >
など、そのままでは出力できない文字があるため、
それらの文字を出力できるよう、変換してやる。
また、空白で始まる行を段落の先頭と認識させるため、その行の
前に段落を示す <p>
をひとつ挿入してやる。
大雑把な流れについては 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/&/\&/g' -e 's/</\</g' -e 's/>/\>/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