2017/01/07

オライリー本の表紙に描かれている全動物をシェル芸でリスト化してみる

O’Reilly Media, Inc.(日本法人: オライリー・ジャパン、以下ひと纏めにしてオライリー社とします)は、コンピュータ技術書籍の出版や、ウェブサイト作成、IT関連のカンファレンスの開催等を手がけている、メディア企業です。
このオライリー社の出版物として、表紙に動物の木版画が描かれているコンピュータ技術書、通称「アニマルシリーズ」があります。
このアニマルシリーズは、詳細な解説や情報量の多さに定評がある事で有名ですが、僕のような動物好きな輩には、表紙にリアルで多様な動物が描かれている点も、非常に魅力的です。
可愛くてつい購入してしまう、なかなかアコギな商法だぜ!

ある時、オライリー社の出版物一覧を眺めていて、アニマルシリーズにはどんな動物が登場したのだろうかと、ふと疑問に感じました。
そこで、今回、アニマルシリーズに描かれている全動物を、シェル芸で調べることが出来ないか、考えてみました。

本記事では、オライリー社の「アニマルシリーズ」の表紙に描かれている全動物をワンライナーでリスト化する方法を、記します。



実行環境

  • Arch Linux 4.4.39-1-lts

  • GNU bash 4.4.5

  • GNU coreutils 8.26-1

  • grep (GNU grep) 2.27

  • sed (GNU sed) 4.2.2

  • gawk 4.1.4

  • nkf 2.1.4

  • curl 7.51.0


方針

オライリー社のWebサイトでは、次のページにて、アニマルシリーズで使用された動物を調べることが出来ます。
http://www.oreilly.com/animals.csp - Animal Menagerie
今回は、このWebページの内容を利用します。

ただし、上記のWebページでは、20件ずつしか、書籍情報を出力できません。
そこで、URLにクエリを与えてやる事によって、全ての書籍情報が得られるまで、ページの取得を続けます。
ページの取得が完了したら、シェル芸ではお馴染みのフィルタコマンドを用いて、HTMLソースから必要な情報を、少しずつ削り出していきます。
最後に出力を整形すれば、アニマルシリーズで使用された全動物のリストが得られます。


シェル芸コード

以下、シェル芸による解答です。

#!/bin/sh

seq 0 20 inf                        | # URLクエリの生成
awk '{print "\
    curl -s http://www.oreilly.com/animals.csp?x-o="$0" \
    |nkf -Lu \
    |sed '\''s/Regul.* kurz & gut/Regular Expression Pocket Reference, 2nd Edition/'\'' \
    |grep -E '\''class=\"(book-title|animal-name)\"'\'' \
    || exit 0\
    "}'                             | # コマンド列の生成
                                        ## curl: URLクエリ毎にHTMLソースを取得
                                        ## nkf: 文字エンコーディングをUTF-8に変換(Regular Expression Pocket Reference, 2nd Edition用)
                                        ## sed: 独語タイトルを英語タイトルに修正(Regular Expression Pocket Reference, 2nd Edition用)
                                        ## grep: 書籍名および動物名レコードの抽出
                                        ## || exit: 番兵コマンド
sh                                  | # コマンドの実行
tr -d '\n'                          | # 改行の除去
sed 's;</h2>;&\n;g'                 | # <h2>タグの後に改行を挿入
sed 's;</h1>;&{FS};g'               | # <h1>タグの後にフィールドセパレータ{FS}を挿入
sed 's/<[^>]*>//g'                  | # HTMLタグの除去
sed 's/\s\{2,\}//g'                 | # 2個以上続くスペースの除去
                                        ### 1.書籍名 2.動物名
awk -F'{FS}' '$0=$2'                | # 動物名フィールドの抽出
                                        ### 1.動物名
awk -vFS= 'OFS=""; $1=toupper($1)'  | # 頭文字を大文字に変換
sort                                | # ソート
uniq                                  # 重複レコードの除去

以下、コピペ実行用のワンライナーコードです。

seq 0 20 inf | awk '{print "curl -s http://www.oreilly.com/animals.csp?x-o="$0" |nkf -Lu |sed '\''s/Regul.* kurz & gut/Regular Expression Pocket Reference, 2nd Edition/'\'' |grep -E '\''class=\"(book-title|animal-name)\"'\'' || exit 0"}' | sh | tr -d '\n' | sed 's;</h2>;&\n;g' | sed 's;</h1>;&{FS};g' | sed 's/<[^>]*>//g' | sed 's/\s\{2,\}//g' | awk -F'{FS}' '$0=$2' | awk -vFS= 'OFS=""; $1=toupper($1)' | sort | uniq

コードの解説

以下、コード中のコメントでは説明しきれていない部分や、補足が必要な箇所について、解説をします。

04行目の awk

ここでは、データを取得・加工する為のコマンドを生成しています。

今回、URLにクエリを与えてやる事によって、書籍情報を順次取得していきます。
しかし、ここでは、どこまでクエリを与えてやれば良いのか分からない、終了条件が不明という問題点があります。
そこで、今回、終了条件として、 grep の終了ステータスを用いる事にしました。

seq で生成したクエリを curl にURLとして与えてやり、HTMLソースを取得する事にします。
ここで、表示する書籍情報が無い場合、HTMLソースに class="book-title" もしくは class="animal-name" の文字列が含まれていない事を利用します。
通常、 grep は、パターンに一致するレコードが見つからなかった場合、非ゼロの終了ステータスを返します。
そこで、 grep の後に、直前に実行したコマンドの終了ステータスが非ゼロの場合のみに実行する処理として、 || exit を追記します。
こうする事によって、書籍情報が無いページが得られた場合、 grep が非ゼロの終了ステータスを返し、その結果 exit が実行され、一連のコマンド列の実行が終了されます。

また、ここでは nkf による文字エンコーディングの変換と、 sed による文字列の置換をおこなっています。
その理由は、書籍『Regular Expression Pocket Reference, 2nd Edition』の参照先が不正である事にあります。
この書籍だけ、何故か参照先が英語版ではなく、下記URLのドイツ語版になっています(指定ミス??)。
https://www.oreilly.de/buecher/120244/-regul%C3%A4re-ausdr%C3%BCcke---kurz-%26-gut.html

ドイツ語版のタイトルには、非ASCIIの文字が含まれているため、そのままでは書籍名のレコード全体が、 grep 側ではバイナリ列と判定されてしまいます。
そこで、 nkf を使って文字エンコーディングを変換することで、 grep で書籍名レコードを抽出出来るようにします。
また、抽出しただけでは書籍タイトルがドイツ語版のままであるため、 sed で英語タイトルに修正しています。


19行目の sed

フィールドの区切り文字、フィールドセパレータには、一般的に半角スペースや , カンマ等が用いられます。
しかし、今回扱うデータでは、各フィールドの値に半角スペースやカンマが使用されています。

ですが、 awk では、フィールドセパレータは文字だけでなく、文字列や正規表現も利用可能です。
そこで、今回、フィールド値として用いられる可能性が低い文字列、 {FS} を、フィールドセパレータとして使いました。 19行目の sed では、このフィールドセパレータ {FS} を挿入する処理をおこなっています。
また、23行目の awk では、実際にフィールドセパレータに {FS} を指定しています。


参照Webサイト

  • http://www.oreilly.com/animals.csp (Sat Jan 7 19:16:16 JST 2017 アクセス)
    → O’Reilly MediaのWebサイト内にある、アニマルシリーズで使用した動物を調べられるページ。


雑記

  • 今回得られたリストを用いて、技術カテゴリ毎、もしくは動物の分類(属・科)毎に集計してみると、傾向が見えてきて面白いかもしれない。
    → ただ…​ 書籍のトピックを技術毎にカテゴリ化するのは、元データには該当する情報がないので、難しいかもしれない。
    → また、動物の分類毎にカテゴリ化するのも、動物名を学名に変換する必要があるので、こっちも難しいかも…​

  • 動物の英語名称を学名に変換するのって、シェル芸で上手く出来ないのかな…​


  • 今回、オライリーの書籍をリスト化してみて気づいた。
    → 日本語版が出版されていないオライリー書籍って、けっこう多いみたい。

  • 日本語版が存在しない書籍のうち、以下の物が面白そう。


(追記)

Twitterにて @Heliac1999さんに教えていただいたテクニック。
curl では、URLを特定の数値範囲として展開したり、更に数値の増分の指定等も出来るとの事。

このテクニックを利用すれば、ややこしいコマンド列を生成するなんて事をしなくても、もっとシンプルに実現できそう!
…​てか、ページ内に最大レコード数が書いてあったのねorz

curl -s http://www.oreilly.com/animals.csp?x-o="[0-$(curl -s http://www.oreilly.com/animals.csp |grep 'class="pagination pagination--top"' |grep -o 'of [0-9]*' |cut -d ' ' -f 2):20]" |
tr -d '\n'                             |
sed 's;</h2>;&\n;g'                    |
sed 's;</h1>;&{FS};g'                  |
sed 's/<[^>]*>//g'                     |
sed 's/\s\{2,\}//g'                    |
awk -F'{FS}' '$0=$2'                   |
awk -vFS= 'OFS=""; $1=toupper($1)'     |
sort                                   |
uniq

コマンド置換を使って無理やりワンライナー化したので、1行目の curl が長ったらしくなってしまったけれど…​
それでも、元のコードよりはシンプル&分かりやすくなったはず。感謝!

Tags: シェル芸 Unix
このエントリーをはてなブックマークに追加