「テキスト処理」の版間の差分

提供: Wikinote
移動: 案内検索
(高効率単語帳メーカー (構想中))
(IP アドレス的なもの抽出)
 
(同じ利用者による、間の13版が非表示)
行1: 行1:
以下のページも参照のこと。
+
[[awk]], [[sed]], [[grep]] のページも参照のこと。
* [[awk]]
+
* [[sed]]
+
* [[grep]]
+
 
+
  
 
== 一行野郎 ==
 
== 一行野郎 ==
行21: 行17:
 
=== IP アドレス的なもの抽出 ===
 
=== IP アドレス的なもの抽出 ===
 
面倒なので、先頭の 0 も許す。
 
面倒なので、先頭の 0 も許す。
  $ egrep -o '([0-9]{1,3}\.){3}[0-9]{1,3}' FILE
+
  $ 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 タグをカットしながら保存した方が効率的だが、後のことも考えて今はそのまま保存することにしよう。
とりあえず 1000 ページ取得することにして、続きはまた今度。
+
とりあえず新しめの 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 や実体参照 (&amp;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, sed, grep のページも参照のこと。

一行野郎

最長行の長さを求める

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 や実体参照 (&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

念のため解説。

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

これで単語帳は完成した。

結論

よく出る単語はほとんど知っている。