第32回シェル芸勉強会 Q8 の解答 + 今後の投稿ネタ

前置き

今日は第32回シェル芸勉強会のQ8の解答です。
また、記事後方に今後の投稿ネタをメモります。

第32回シェル芸勉強会の問題は次のURLにあります。
【問題のみ】jus共催 第32回全くインスタ映えしないシェル芸勉強会 | 上田ブログ

シェル芸勉強会の解答は、思考を忠実にアウトプットすることを目標にしていますが、徐々に雑になってきている気がします。

Q8 問題(上記URLからコピー)

次のようなテキストについて、漢字やカタカナが行頭に来るように改行を入れるワンライナーを考えてください。ただし、「シェル芸」のようにカタカナ+漢字のものは1単語として扱い、改行を入れないでください。この問題については一般解を考えてみましょう。

$ cat japanese.txt
ん僕らは既に死んでいる
死んでいるからシェル芸だ。

出力を示します。最初の「ん」は独立した行に出力してください。

ん
僕らは
既に
死んでいる
死んでいるから
シェル芸だ。

問題を見た感想・補足

すごい形態素解析したいけど、たぶん役に立たない。

試行1

役に立たないって第1印象だけど良く見たらいけそうです。
「カタカナ+漢字」のところがめんどくさそうですが、↓を見た感じいけそうです。

$ cat japanese.txt | iconv -t euc-jp | mecab  | iconv -f euc-jp | sed 's#,.*##'
ん      助動詞
僕ら    名詞
は      助詞
既に    副詞
死んで  動詞
いる    接尾辞
EOS
死んで  動詞
いる    接尾辞
から    助詞
シェル  名詞
芸      名詞
だ      判定詞
。      特殊
EOS

解答1(邪道)

$ cat japanese.txt | iconv -t euc-jp | mecab -u shellgei.dic | iconv -f euc-jp | sed 's#,.*##' | grep -v EOS | awk '$2=="名詞"||$2=="動詞"||$2=="副詞"{print b;b=""}{b=b$1}END{print b}'
ん
僕らは
既に
死んでいる
死んでいるから
シェル芸だ。

解説

今回のデータだと(問題文無視)自立語を前に持ってくるとそれっぽくなりそうです。
awk で、自立語以外だとバッファに貯めて、自立語が来たらバッファを出力という形で改行します。

$ cat japanese.txt | iconv -t euc-jp | mecab  | iconv -f euc-jp | sed 's#,.*##' | grep -v EOS | awk '$2=="名詞"||$2=="動詞"||$2=="副詞"{print b;b=""}{b=b$1}END{print b}'
ん
僕らは
既に
死んでいる
死んでいるから
シェル
芸だ。

ハードルとなるのは、やはり「カタカナ+漢字」の「シェル芸」です。
自然言語解析は辞書がほぼすべてらしいですし、「シェル芸」を一般的な単語だと認識していないIPAが悪いということで解答完了にしても問題ないと個人的には思います。
が、まぁ問題に書かれている形になっていないのは寝覚めが悪いので、手で辞書に登録してやれば望みの解答が得られるように思います。

まず、辞書のもとになるCSVファイルを作成します。以下が作成したものです。(mecabを使っているときからですが、Ubuntu(WSL)でaptできるデフォルトのIPA辞書はEUC-JPなので文字コードiconvでよしなに変換しています。Ubuntuだと、mecab-ipadic-utf8 をインストールすると
UTF-8で使用できます。 @ebanさんありがとうございます! eban on Twitter: "@nogiro_iota Ubuntuならmecab-ipadic-utf8をインストールすればUTF-8の辞書になりますよ #シェル芸"

$ cat shellgei_dict.csv | iconv -f euc-jp
シェル芸,0,0,0,名詞,固有名詞,*,*,*,*,しぇるげい,シェルゲイ,シェルゲイ

上記CSVファイルから辞書をコンパイルします。

$ /usr/lib/mecab/mecab-dict-index -d /var/lib/mecab/dic/debian -u shellgei.dic -f euc-jp -t euc-jp shellgei_dict.csv
/var/lib/mecab/dic/debian/pos-id.def is not found. minimum setting is used
reading shellgei_dict.csv ... 1
emitting double-array: 100% |###########################################|

done!

コンパイルした辞書を使うようmecabにオプションを付けると解答になります。

$ cat japanese.txt | iconv -t euc-jp | mecab -u shellgei.dic | iconv -f euc-jp | sed 's#,.*##' | grep -v EOS | awk '$2=="名詞"||$2=="動詞"||$2=="副詞"{print b;b=""}{b=b$1}END{print b}'
ん
僕らは
既に
死んでいる
死んでいるから
シェル芸だ。

こっちのほうがカタカナ・漢字より一般的だから許して!

解答2

cat japanese.txt | tr -d \\n | iconv -t sjis | xxd -ps | tr -d \\n | awk 1 | fold -w 4 | tr a-z A-Z | cat <(echo 'ibase=16') - | bc | awk 'BEGIN{p=0}p==1&&!('$((0x829f))'<=$0&&$0<='$((0x82f1))'){b=b" "$0}p==0&&!('$((0x829f))'<=$0&&$0<='$((0x82f1))'){printf b"\n"; b=$0;p=1}'$((0x829f))'<=$0&&$0<='$((0x82f1))'{b=b" "$0;p=0}' | sed 's#$# '$((0x0a))'#' | tr \  \\n | cat <(echo 'obase=16') - | bc | sed 's#^A$#0A#' | tr -d \\n | tr A-Z a-z | xxd -p -r | iconv -f sjis

解説2

UTF-8はマルチバイトなのでめんどくさいですが、SJISだと2バイト文字でかつ、ひらがなが固まったコードが振られていると期待できます。

文字コード表 シフトJIS(Shift_JIS) を見ると実際にひらがなは、0x82010x8283 であることがわかります。
(今回の問題は、カタカナ・漢字に注目するより、その2集合は、ひらがなの補集合であると考えたほうが単純なのでそう考えています。)

iconv文字コードを変換したあとは、10進数にしたり awkで整形したりしています。
次はawkの出力です。(解答1のものとほぼ変わりません。自立語・付属語の関係がひらがな以外・ひらがな、になった以外に、ひらがな以外の連続に改行を挿れないようにしているだけです。)

$ cat japanese.txt | tr -d \\n | iconv -t sjis | xxd -ps | tr -d \\n | awk 1 | fold -w 4 | tr a-z A-Z | cat <(echo 'ibase=16') - | bc | awk 'BEGIN{p=0}p==1&&!('$((0x829f))'<=$0&&$0<='$((0x82f1))'){b=b" "$0}p==0&&!('$((0x829f))'<=$0&&$0<='$((0x82f1))'){printf b"\n"; b=$0;p=1}'$((0x829f))'<=$0&&$0<='$((0x82f1))'{b=b" "$0;p=0}'
 33521
38508 33511 33485
35577 33481
36480 33521 33477 33442 33513
36480 33521 33477 33442 33513 33449 33511
33622 33606 33675 35964 33470

ここに改行コードをつけたり、バイナリから戻したり、文字コードを戻したりすると、解答になります。

$ cat japanese.txt | tr -d \\n | iconv -t sjis | xxd -ps | tr -d \\n | awk 1 | fold -w 4 | tr a-z A-Z | cat <(echo 'ibase=16') - | bc | awk 'BEGIN{p=0}p==1&&!('$((0x829f))'<=$0&&$0<='$((0x82f1))'){b=b" "$0}p==0&&!('$((0x829f))'<=$0&&$0<='$((0x82f1))'){printf b"\n"; b=$0;p=1}'$((0x829f))'<=$0&&$0<='$((0x82f1))'{b=b" "$0;p=0}' | sed 's#$# '$((0x0a))'#' | tr \  \\n | cat <(echo 'obase=16') - | bc | sed 's#^A$#0A#' | tr -d \\n | tr A-Z a-z | xxd -p -r | iconv -f sjis
ん
僕らは
既に
死んでいる
死んでいるから
シェル芸だ

まとめ

mecabは楽しい。
バイナリは汎用的に便利ですが、場合によっては普通でない文字コード(geditいわく)でないSJISを使ったほうが便利なことが多い。

感想

形態素解析できて満足しました。後ろの正統なやり方はやっつけで、解説もやっつけになってしまいました。



今後の投稿ネタについて

アウトプットを習慣づけるのが目的なので、シェル芸勉強会の問題を解き終わっても継続して投稿する予定です(したいなー)。
いま考えてるのは以下です。何もなければ明日は構築しようとしてるおうち仮想環境について投稿します。

  • ポチったPCの話 -> おうち仮想環境を構築したい
  • Raspberry Pi で3軸加速度を取れるようになった話・目的
  • シェル芸関係
    • 思いついている別解
    • ワンライナーで使うコマンドの逆引き的なもの(「縦に並べたい」→ xargs -n 1