「テキスト処理」の版間の差分
(→高効率単語帳メーカー (構想中)) |
細 (→IP アドレス的なもの抽出) |
||
(同じ利用者による、間の13版が非表示) | |||
行1: | 行1: | ||
− | + | [[awk]], [[sed]], [[grep]] のページも参照のこと。 | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
== 一行野郎 == | == 一行野郎 == | ||
行21: | 行17: | ||
=== IP アドレス的なもの抽出 === | === IP アドレス的なもの抽出 === | ||
面倒なので、先頭の 0 も許す。 | 面倒なので、先頭の 0 も許す。 | ||
− | $ egrep -o ' | + | $ egrep -o '[0-9]{1,3}(\.[0-9]{1,3}){3}' FILE |
+ | もう少し長くして、先頭の 0 を許さないもの。(0 のみなら OK) | ||
+ | $ egrep -o '(0|[1-9][0-9]{0,2})(\.(0|[1-9][0-9]{0,2})){3}' FILE | ||
=== URL 的なもの抽出 === | === URL 的なもの抽出 === | ||
使える記号は適当に。 | 使える記号は適当に。 | ||
− | $ egrep -o '[a-z]+://[[:alnum:]:;/._!?~%#&=+-]+' | + | $ egrep -o '[a-z]+://[[:alnum:]:;/._!?~%#&=+-]+' FILE |
=== 文字単位で逆順に出力 === | === 文字単位で逆順に出力 === | ||
行61: | 行59: | ||
=== 行を長さ順にソート === | === 行を長さ順にソート === | ||
− | + | これ、意外と難しい。もっと美しい方法がないだろうか。。 | |
$ awk '{printf("%4d ",length()); print}' FILE | sort -n | cut -c 6- | $ awk '{printf("%4d ",length()); print}' FILE | sort -n | cut -c 6- | ||
+ | 行の長さを付与して sort した後に、行の長さ部分をカットしている。 | ||
== 企画モノ == | == 企画モノ == | ||
− | === 高効率単語帳メーカー | + | === 高効率単語帳メーカー === |
− | + | 高効率単語帳とは、英語の文書や文献データベースに頻出する単語と、それらの意味を列挙した単語帳である。 | |
− | + | 頻出するけれども知らない、という単語から学習していくことで、その文献に対する読解力を効率的に上げることが可能… のはず。 | |
自動生成に必要なパーツを考えてみよう。 | 自動生成に必要なパーツを考えてみよう。 | ||
* クローラ | * クローラ | ||
* HTML タグなどの不要部分削除 | * HTML タグなどの不要部分削除 | ||
− | * | + | * 英単語の抽出と数え上げ |
− | + | ||
* 不要英単語削除 (the, is など。手作業か?) | * 不要英単語削除 (the, is など。手作業か?) | ||
* 単語の意味抽出 | * 単語の意味抽出 | ||
行97: | 行95: | ||
HTML タグをカットしながら保存した方が効率的だが、後のことも考えて今はそのまま保存することにしよう。 | HTML タグをカットしながら保存した方が効率的だが、後のことも考えて今はそのまま保存することにしよう。 | ||
− | + | とりあえず新しめの 250001 から 251000 までの 1000 ページを取得することにした。 | |
+ | |||
+ | Red Hat Bugzilla にはアクセスできないページが存在し、その場合 "Access Denied" というページが表示される。 | ||
+ | このページは不要なので削除しよう。 | ||
+ | |||
+ | #!/bin/bash | ||
+ | for file in html/*; do | ||
+ | grep -q 'Access Denied' $file | ||
+ | if [ $? -eq 0 ]; then | ||
+ | rm -f $file | ||
+ | fi | ||
+ | done | ||
+ | |||
+ | grep は文字列が見つかると返り値が 0 になるので、それを利用した。 | ||
+ | およそ 1/4 のページがアクセス不可で削除されてしまった。 | ||
+ | |||
+ | $ ls html/ | wc -l | ||
+ | 733 | ||
+ | |||
+ | ==== HTML タグなど不要部分を削除する ==== | ||
+ | |||
+ | さて、次は HTML ファイルから不要な部分を削除する部分だが、まずは HTML タグを削除することを考える。 | ||
+ | |||
+ | $ sed 's/<[^>]*>//g' FILE.html | ||
+ | |||
+ | この sed コマンドで OK ... と思ったが、そう簡単にはいかなかった。 | ||
+ | '''複数行にわたるタグ''' があるのだ。例えばこんなの: | ||
+ | <link href="skins/standard/global.css" | ||
+ | rel="stylesheet" | ||
+ | type="text/css"> | ||
+ | |||
+ | これはプログラミングが必要か…。 | ||
+ | |||
+ | と思ったが、要は 1 行なら sed で簡単に処理できるのである。 | ||
+ | '''じゃあ 1 行にしてしまえばいいじゃない。'''ということで、'''奥義「改行ヌル文字変換ッ!!」''' | ||
+ | |||
+ | $ cat FILE | tr '\n' '\0' | sed 's/<[^>]*>//g' | tr '\0' '\n' | ||
+ | |||
+ | これで無駄な部分はほとんど削除することができる。 | ||
+ | あとは、JavaScript や実体参照 (&nbsp; など)、無駄な空白や空行を削除すれば、 | ||
+ | HTML をテキストデータに変換できる。 | ||
+ | |||
+ | #!/bin/bash | ||
+ | for html in html/*; do | ||
+ | BASENAME=$(basename $html .html) | ||
+ | DATA=$(cat $html | sed '/script type/,/\/script/d' | sed 's/&[^;]*;//g' \ | ||
+ | | tr '\n' '\0' | sed 's/<[^>]*>//g' | tr '\0' '\n' \ | ||
+ | | sed -e '/^\s*$/d' -e 's/^ *//' -e 's/ *$//') | ||
+ | echo "$DATA" > text/$BASENAME.txt | ||
+ | done | ||
+ | |||
+ | 念のため解説。 | ||
+ | ; <code>sed '/script type/,/\/script/d'</code> | ||
+ | : JavaScript は開始タグと終了タグが別の行、かつ script タグと同じ行にテキストは含まれないと仮定し、script タグを含めて削除する。 | ||
+ | ; <code>sed 's/&[^;]*;//g'</code> | ||
+ | : & で始まり ; で終わる実体参照を削除する。 | ||
+ | ; <code>tr '\n' '\0' | sed 's/<[^>]*>//g' | tr '\0' '\n'</code> | ||
+ | : 今回のミソ。改行をヌル文字に変換することで sed へ 1 行で渡し、タグをすべて削除する。その後、ヌル文字を改行に戻す。 | ||
+ | ; <code>sed -e '/^\s*$/d' -e 's/^ *//' -e 's/ *$//'</code> | ||
+ | : 先頭から、空白のみを含む行を削除、先頭の空白列を削除、末尾の空白列を削除。 | ||
+ | |||
+ | ==== 単語の抽出と数え上げ ==== | ||
+ | |||
+ | さて、この部分をどうするかだが、大きく分けて 2 つ方法がある。 | ||
+ | * プログラミングせずにメモリと時間を大量消費して処理する。 | ||
+ | * プログラミングして効率的に処理する。 | ||
+ | |||
+ | マシンパワーはないがメモリは大量にあるので、ここはポリシーに従い、 | ||
+ | 前者の方法でやってみることにする。お気づきの方も多いかもしれないが、 | ||
+ | 一行野郎で紹介している、[[#頻出コマンド Top 10|頻出コマンド Top 10]] | ||
+ | のパイプテクニックを利用すれば簡単に算出することができる。 | ||
+ | |||
+ | #!/bin/bash | ||
+ | for file in text/* | ||
+ | do | ||
+ | cat $file | ||
+ | done | egrep -o '<nowiki>[[:alpha:]]</nowiki>+' | sort | uniq -c | sort -nr > ranking.txt | ||
+ | |||
+ | 733 ファイルにかかった時間は 7.5 秒。 | ||
+ | 結果は以下のようになった。 | ||
+ | |||
+ | 11605 the | ||
+ | 10003 to | ||
+ | 7990 a | ||
+ | 7747 of | ||
+ | 7468 EDT | ||
+ | 6474 this | ||
+ | 5813 in | ||
+ | 5384 bug | ||
+ | 5152 is | ||
+ | 4849 Bug | ||
+ | : | ||
+ | |||
+ | ザッと見た感じ、3 文字以下の単語はいらなそうだし、 | ||
+ | 大文字小文字を統一すべきということに気づいたので、修正してみる。 | ||
+ | |||
+ | #!/bin/bash | ||
+ | for file in text/* | ||
+ | do | ||
+ | cat $file | ||
+ | done | tr A-Z a-z | egrep -o '[a-z]{4,}' | sort | uniq -c | sort -nr > ranking.txt | ||
+ | |||
+ | だいぶ結果が有用な感じになってきた。 | ||
+ | |||
+ | 8985 this | ||
+ | 6433 from | ||
+ | 6116 comment | ||
+ | 3930 fedora | ||
+ | 3446 that | ||
+ | 3402 version | ||
+ | 3045 last | ||
+ | 2997 search | ||
+ | 2272 clone | ||
+ | 2271 results | ||
+ | : | ||
+ | |||
+ | もっとうまく不要な単語を削れればいいのだが…。 | ||
+ | |||
+ | ==== 単語の意味抽出 ==== | ||
+ | |||
+ | これはあまり詳しく書けないのだが、あるサイトから単語のデータを落としてきて | ||
+ | ちょっと整形してやると、うまいこと 1 行で単語の意味を取り出せる。 | ||
+ | オレはこれをすぐ呼べるようにしていて (dic コマンド)、カーネルソースを読んでいるときなどに | ||
+ | コメントにわからない単語が出てきたら、vi からさくっとそのまま辞書を引けるようにしている。 | ||
+ | これがいい。 | ||
+ | |||
+ | #!/bin/bash | ||
+ | for word in "$@"; do | ||
+ | word=$(echo $word | tr ' ' '+') | ||
+ | curl http://hogehoge/$word/ 2> /dev/null | grep -A 1 midashi | head -n 2 | sed -e 's/<\/ol>/\n/g' -e 's/<[^>]*>//g' | ||
+ | done | ||
+ | |||
+ | $ dic linux unix | ||
+ | LINUX | ||
+ | 【名】《コ》リナックス、リヌクス、ライナックス◆ボランティア(ヘルシンキ大学のLinus B. Torvalds等)が | ||
+ | 開発推進した無料OS。Redhat、Slackware、Debian、Yggdrasilなどの名前でも配布。UNIX系 | ||
+ | |||
+ | Unix | ||
+ | 【商標】ユニックス◆米AT&T社のベル研究所が1968年に開発したオペレーティングシステム(OS)。 | ||
+ | |||
+ | ==== 単語帳を出力する ==== | ||
+ | |||
+ | 上で出力したランキングデータを処理していく。 | ||
+ | |||
+ | #!/bin/sh | ||
+ | RANKING=$(awk '{ print $2 }' ranking.txt | head -n 100) | ||
+ | for WORD in $RANKING; do | ||
+ | ./dict.sh $WORD | ||
+ | sleep 5 | ||
+ | done | ||
+ | |||
+ | これで単語帳は完成した。 | ||
+ | |||
+ | ==== 結論 ==== | ||
+ | |||
+ | よく出る単語はほとんど知っている。 |
2011年2月25日 (金) 18:47時点における最新版
一行野郎
最長行の長さを求める
AWK のマニュアルにも載っている、基礎的な一行。
$ awk '{ if (max > length()) max = length() } END { print max }' FILE
テキストファイルを横に連結
これは paste コマンドを知っているかどうか。
$ paste FILE1 FILE2
頻出コマンド Top 10
ぜんぜん役には立たないが、パイプの真骨頂が味わえる、趣き深い一行。
$ history | awk '{ print $2 }' | sort | uniq -c | sort -nr | head -n 10
IP アドレス的なもの抽出
面倒なので、先頭の 0 も許す。
$ egrep -o '[0-9]{1,3}(\.[0-9]{1,3}){3}' FILE
もう少し長くして、先頭の 0 を許さないもの。(0 のみなら OK)
$ egrep -o '(0|[1-9][0-9]{0,2})(\.(0|[1-9][0-9]{0,2})){3}' FILE
URL 的なもの抽出
使える記号は適当に。
$ egrep -o '[a-z]+://[[:alnum:]:;/._!?~%#&=+-]+' FILE
文字単位で逆順に出力
行単位で逆順に出力する場合は、cat の反対である
$ tac FILE
でよいが、これじゃあ生ぬるい (何が)。文字単位で逆転させようじゃないか。
$ tac -rs '[^@]' FILE
@ はファイル中に出現していない文字に置き換えること。 要するに、ファイル中のすべての文字を区切り文字として逆転させている。
大文字・小文字変換
小文字をすべて大文字に変換する。下記はどちらも同じ結果を得る。
$ tr 'a-z' 'A-Z' < FILE $ awk '{ print toupper($0) }' FILE
ランダムに 1 行出力
0 <= rand() < 1
であることに注意。
$ awk '{l[NR]=$0} END{srand();print l[int((rand()*NR))+1]}' FILE
区切り文字を変換
たぶん tr が一番簡単。
$ tr -s ' ' ',' < FILE
行番号を付ける
以下はどちらもほぼ同等。特に nl は細かい設定ができるようだが、これは man 参照のこと。
$ cat -n FILE $ nl -ba FILE
最初の N 行をカットする
$ tail -n +$((N+1)) FILE
要するに、1 行目だけいらない (2 行目以降を出力したい) 場合、
$ tail -n +2 FILE
あ、awk でもいいか。こっちの方がわかりやすいし。
$ awk 'NR > 1' FILE
行を長さ順にソート
これ、意外と難しい。もっと美しい方法がないだろうか。。
$ awk '{printf("%4d ",length()); print}' FILE | sort -n | cut -c 6-
行の長さを付与して sort した後に、行の長さ部分をカットしている。
企画モノ
高効率単語帳メーカー
高効率単語帳とは、英語の文書や文献データベースに頻出する単語と、それらの意味を列挙した単語帳である。 頻出するけれども知らない、という単語から学習していくことで、その文献に対する読解力を効率的に上げることが可能… のはず。
自動生成に必要なパーツを考えてみよう。
- クローラ
- HTML タグなどの不要部分削除
- 英単語の抽出と数え上げ
- 不要英単語削除 (the, is など。手作業か?)
- 単語の意味抽出
- 統合・整形
といったところか。
まずは Red Hat Bugzilla の高効率単語帳を作ることにしよう。 作成ポリシーは「できるだけプログラミングしない」こと。 bash と Linux のコマンドを駆使してどこまでできるか挑戦してみる。
クローラ
Red Hat Bugzilla では、URL が以下の形式になっているのでクロールするのは簡単そうだ。
https://bugzilla.redhat.com/show_bug.cgi?id=100
これは bash と curl ですぐにできるだろう。 とりあえず作ることを優先にするので、引き数やエラーに対する考慮は行わない。
#!/bin/bash for id in $(seq 250001 251000); do curl -o html/$id.html "https://bugzilla.redhat.com/show_bug.cgi?id=$id" sleep 5 # これを短くしてはいけない done
HTML タグをカットしながら保存した方が効率的だが、後のことも考えて今はそのまま保存することにしよう。 とりあえず新しめの 250001 から 251000 までの 1000 ページを取得することにした。
Red Hat Bugzilla にはアクセスできないページが存在し、その場合 "Access Denied" というページが表示される。 このページは不要なので削除しよう。
#!/bin/bash for file in html/*; do grep -q 'Access Denied' $file if [ $? -eq 0 ]; then rm -f $file fi done
grep は文字列が見つかると返り値が 0 になるので、それを利用した。 およそ 1/4 のページがアクセス不可で削除されてしまった。
$ ls html/ | wc -l 733
HTML タグなど不要部分を削除する
さて、次は HTML ファイルから不要な部分を削除する部分だが、まずは HTML タグを削除することを考える。
$ sed 's/<[^>]*>//g' FILE.html
この sed コマンドで OK ... と思ったが、そう簡単にはいかなかった。 複数行にわたるタグ があるのだ。例えばこんなの:
<link href="skins/standard/global.css" rel="stylesheet" type="text/css">
これはプログラミングが必要か…。
と思ったが、要は 1 行なら sed で簡単に処理できるのである。 じゃあ 1 行にしてしまえばいいじゃない。ということで、奥義「改行ヌル文字変換ッ!!」
$ cat FILE | tr '\n' '\0' | sed 's/<[^>]*>//g' | tr '\0' '\n'
これで無駄な部分はほとんど削除することができる。 あとは、JavaScript や実体参照 ( など)、無駄な空白や空行を削除すれば、 HTML をテキストデータに変換できる。
#!/bin/bash for html in html/*; do BASENAME=$(basename $html .html) DATA=$(cat $html | sed '/script type/,/\/script/d' | sed 's/&[^;]*;//g' \ | tr '\n' '\0' | sed 's/<[^>]*>//g' | tr '\0' '\n' \ | sed -e '/^\s*$/d' -e 's/^ *//' -e 's/ *$//') echo "$DATA" > text/$BASENAME.txt done
念のため解説。
-
sed '/script type/,/\/script/d'
- JavaScript は開始タグと終了タグが別の行、かつ script タグと同じ行にテキストは含まれないと仮定し、script タグを含めて削除する。
-
sed 's/&[^;]*;//g'
- & で始まり ; で終わる実体参照を削除する。
-
tr '\n' '\0' | sed 's/<[^>]*>//g' | tr '\0' '\n'
- 今回のミソ。改行をヌル文字に変換することで sed へ 1 行で渡し、タグをすべて削除する。その後、ヌル文字を改行に戻す。
-
sed -e '/^\s*$/d' -e 's/^ *//' -e 's/ *$//'
- 先頭から、空白のみを含む行を削除、先頭の空白列を削除、末尾の空白列を削除。
単語の抽出と数え上げ
さて、この部分をどうするかだが、大きく分けて 2 つ方法がある。
- プログラミングせずにメモリと時間を大量消費して処理する。
- プログラミングして効率的に処理する。
マシンパワーはないがメモリは大量にあるので、ここはポリシーに従い、 前者の方法でやってみることにする。お気づきの方も多いかもしれないが、 一行野郎で紹介している、頻出コマンド Top 10 のパイプテクニックを利用すれば簡単に算出することができる。
#!/bin/bash for file in text/* do cat $file done | egrep -o '[[:alpha:]]+' | sort | uniq -c | sort -nr > ranking.txt
733 ファイルにかかった時間は 7.5 秒。 結果は以下のようになった。
11605 the 10003 to 7990 a 7747 of 7468 EDT 6474 this 5813 in 5384 bug 5152 is 4849 Bug :
ザッと見た感じ、3 文字以下の単語はいらなそうだし、 大文字小文字を統一すべきということに気づいたので、修正してみる。
#!/bin/bash for file in text/* do cat $file done | tr A-Z a-z | egrep -o '[a-z]{4,}' | sort | uniq -c | sort -nr > ranking.txt
だいぶ結果が有用な感じになってきた。
8985 this 6433 from 6116 comment 3930 fedora 3446 that 3402 version 3045 last 2997 search 2272 clone 2271 results :
もっとうまく不要な単語を削れればいいのだが…。
単語の意味抽出
これはあまり詳しく書けないのだが、あるサイトから単語のデータを落としてきて ちょっと整形してやると、うまいこと 1 行で単語の意味を取り出せる。 オレはこれをすぐ呼べるようにしていて (dic コマンド)、カーネルソースを読んでいるときなどに コメントにわからない単語が出てきたら、vi からさくっとそのまま辞書を引けるようにしている。 これがいい。
#!/bin/bash for word in "$@"; do word=$(echo $word | tr ' ' '+') curl http://hogehoge/$word/ 2> /dev/null | grep -A 1 midashi | head -n 2 | sed -e 's/<\/ol>/\n/g' -e 's/<[^>]*>//g' done
$ dic linux unix LINUX 【名】《コ》リナックス、リヌクス、ライナックス◆ボランティア(ヘルシンキ大学のLinus B. Torvalds等)が 開発推進した無料OS。Redhat、Slackware、Debian、Yggdrasilなどの名前でも配布。UNIX系 Unix 【商標】ユニックス◆米AT&T社のベル研究所が1968年に開発したオペレーティングシステム(OS)。
単語帳を出力する
上で出力したランキングデータを処理していく。
#!/bin/sh RANKING=$(awk '{ print $2 }' ranking.txt | head -n 100) for WORD in $RANKING; do ./dict.sh $WORD sleep 5 done
これで単語帳は完成した。
結論
よく出る単語はほとんど知っている。