読者です 読者をやめる 読者になる 読者になる

LPIC 303 試験対策 その2

前回からの続きです。

y-andoh.hateblo.jp

はじめに

前回は、LPI Japan公式例題集(以下参照)
LPIC3例題解説(303試験)|Linux技術者認定機関 LPI-Japan [エルピーアイジャパン] を、ソートしました。

ただ、一つ課題が残っていることに気づきました。
平たく言うと、「すべての問題をプリントアウトしたくないですか?」ということです。
勉強する目的なら、ペーパーメディアも意外とバカにならないですよね。

前回ソートした80問の例題をすべてプリントアウトしたいんですが、これが意外と面倒です。
一問一問ブラウザでページをひらいて、一つづつプリントアウトしますか?辛いですよね?

というわけで、引き続き小細工してみます。

目的は、
LPIC 303の例題をジャンルごとにソートして、ソート済み問題(全80問)をプリントアウトする。」
です。

最終的に得られた結果

以下のリンク先を参照のこと。
sites.google.com

ここの「LPIC 303 例題集」をプリントアウトすればいいです。

作り方

以下は、結果を得るまでの過程です。
いちおう自分用のメモとして書いておきます。

1. ソートして一つにまとめる
前回(LPIC 303 試験対策 - I took an arrow in the knee.)作ったソート済みリンク集のファイル(303_exmaple_sorted.html)から、 cut を使って、"http:"の部分だけを抽出してみます。
以下、その結果。

$ cat 303_exmaple_sorted.html | cut -d \" -f 2 | head -3
http://www.lpi.or.jp/ex/303/ex_1335.shtml
http://www.lpi.or.jp/ex/303/ex_2370.shtml
http://www.lpi.or.jp/ex/303/ex_286.shtml

※説明のため、あえてheadで頭3行だけ表示させています。

区切り文字ダブルクォート(要エスケープ)で囲まれた部分をcutで抜き出しただけです。
このURLをすべて wget の引数に渡せば、欲しい例題ファイルが一括で全て取得できそうです。
そのついでに、計80問(80ファイル)をすべて一つのファイルにまとめてしまいます。

以下、その結果。

$ cat 303_exmaple_sorted.html | cut -d \" -f 2 | xargs wget -O all.tmp
    :
    : (延々とwgetの結果が続きます。長いので省略します。)
    :
$ 
$ ls -l all.tmp 
-rw-rw-r-- 1 user user 3013815  5月 30 16:49 all.tmp

これで、ファイル:all.tmp に、80問の例題をすべてまとめました。

ポイントは、

- wget には、引数で複数のURLを与えることができる。(xargsが使える。)
- wget の -O オプションで出力先ファイルを指定できる。
てところです。まあ難しくはないですね。

ただ、今のファイル:all.tmp には「問題と回答」以外の余計な記述がたくさん含まれています。
なので、このファイルから「問題と回答」の部分だけを抜粋したいと思います。

というわけで、
ファイル:all.tmpの中身を調べると、「問題と回答」は、すべて以下のhtmlタグのブロック内にまとまっていることが分かります。

    <h2 class="title04h2">
              :
    <!- exercise --> ~ <!- /exercise -->
              :
    <!-- answer -->  ~ <!-- /answer -->

タイトル行である <h2 class="title04h2> が書いてある行から、
回答終わりのコメント行である <!-- /answer --> が書いてある行までを抜き出せば良さそうです。

※ 全部の問題を確認したわけではないですが、そう信じてみます。信仰心は大事です。
「楽さと厳密さはトレードオフだから。」と、自分に言い訳しながら手抜きをしてます。

こういう場合、htmlパース用のライブラリを使えばいいんでしょうが、調べたりインストールしたりするのがめんどくさいから、sed使っちゃいます。

やり方は単純です。
「問題の記述が始まる行番号」
「回答の記述が終わる行番号」
を、すべてピックアップして、得られた行番号を、「sed -n」に与えて実行する。
あまり美しくないですが、これで行けると思います。

要は、最終的に以下のような形のコマンドを作りたいわけです。

sed -n -e 248,269p -e 981,1002p -e 1714,1735p all.tmp > 303.html



以下、その解説。
2. "egrep -n" で、行番号をリストアップしてみる

$ egrep -n 'title04h2|<!-- /answer -->' all.tmp | head -6
248:    <h2 class="title04h2"><strong>320.1 </strong><span>OpenSSL</span></h2>
269:    <!-- /answer -->
981:    <h2 class="title04h2"><strong>320.1 </strong><span>OpenSSL</span></h2>
1002:   <!-- /answer -->
1714:   <h2 class="title04h2"><strong>320.1</strong><span>OpenSSL</span></h2>
1735:   <!-- /answer -->

※くどいようですが、headを使っているのは、例として表示させるためだけです。

3. awk を使って、行番号以外の記述はフィルタして、更に開始行と終了行のセットごとにまとめてみる。

$ egrep -n 'title04h2|<!-- /answer -->' all.tmp | awk -F ':' '{if(tmp!=0){print tmp,$1; tmp=0}else{tmp=$1}}' | head -3
248 269
981 1002
1714 1735

※ かなり見苦しい操作になってますね。。。

4. 実際に使うsed文を成形する。

$ egrep -n 'title04h2|<!-- /answer -->' all.tmp | awk -F ':' 'BEGIN{printf("sed -n ")} {if(tmp!=0){printf("-e %s,%sp ",tmp,$1); tmp=0}else{tmp=$1}} END{print "all.tmp > 303.html"}'
sed -n -e 248,269p -e 981,1002p -e 1714,1735p -e 2447,2468p -e 3180,3201p -e 3913,3934p -e 4646,4741p -e 5453,5476p -e 6188,6209p -e 6921,6942p -e 7654,7675p -e 8387,8408p -e 9120,9141p -e 9853,9874p -e 10586,10607p -e 11319,11340p -e 12052,12073p -e 12785,12826p -e 13538,13559p -e 14271,14293p -e 15005,15029p -e 15741,15762p -e 16474,16495p -e 17207,17228p -e 17940,17961p -e 18673,18694p -e 19406,19427p -e 20139,20160p -e 20872,20894p -e 21606,21627p -e 22339,22360p -e 23072,23093p -e 23805,23826p -e 24538,24559p -e 25271,25292p -e 26004,26025p -e 26737,26758p -e 27470,27491p -e 28203,28224p -e 28936,28957p -e 29669,29690p -e 30402,30423p -e 31135,31156p -e 31868,31889p -e 32601,32622p -e 33334,33355p -e 34067,34088p -e 34800,34821p -e 35533,35554p -e 36266,36313p -e 37025,37046p -e 37758,37779p -e 38491,38518p -e 39230,39252p -e 39964,39987p -e 40699,40720p -e 41432,41453p -e 42165,42186p -e 42898,42919p -e 43631,43652p -e 44364,44385p -e 45097,45118p -e 45830,45851p -e 46563,46584p -e 47296,47317p -e 48029,48050p -e 48762,48783p -e 49495,49516p -e 50228,50249p -e 50961,50982p -e 51694,51715p -e 52427,52448p -e 53160,53201p -e 53913,53934p -e 54646,54667p -e 55379,55403p -e 56115,56136p -e 56848,56871p -e 57583,57619p -e 58331,58353p all.tmp > 303.html

実行結果が超長いことになってますが、コマンドとしてはうまく成形できてそうです。
あとはこのコマンドを実行してあげるだけです。
(上の例はコマンドを成形しただけで、実行はしていません。)

5. 成形したsed文を実行する。
実行するのは簡単で、パイプでbashに渡すだけ。
以下、実行結果。

$ egrep -n 'title04h2|<!-- /answer -->' all.tmp | awk -F ':' 'BEGIN{printf("sed -n ")} {if(tmp!=0){printf("-e %s,%sp ",tmp,$1); tmp=0}else{tmp=$1}} END{print "all.tmp > 303.html"}' | bash
$ 
$ ls -l 303.html 
-rw-rw-r-- 1 user user 278425  5月 30 17:59 303.html

303.html に、うまく吐き出せたようですね。
あとはこれをプリントアウトするだけです。
ちなみに、最後に出力した303.htmlファイルは、私のgoogleサイト(lpic303exq) に、貼り付けてあります。
(記事の冒頭にも書いたやつです。)

作り方のまとめ

やっていることは、
「例題を分野別にソートして、ソートした順序で一つのファイルに結合して、結合したファイルから必要部分だけをパースする。」
と、至ってシンプルですが、意外と面倒でしたね。

結構時間かかりましたが楽しかったですね。
いやー、こういうスクレイピングって楽しいなぁ。

だけどですね、試験対策の勉強はそっちのけなんですよね(笑)
一生懸命に例題集は作成したけど、全然勉強してないという状況。
しかも、ついでにブログ記事まで書いている始末。

これって「受験生が参考書買っただけで満足して、そのまま何もやらない。」
という、伝統の受験生あるあると全く同じですよね。

余談

sedを使ってhtmlファイルをパースするのは褒められたやり方では無いような気がします。
良い子は真似しないほうが良さそうです。