自宅仮想環境 - QEMU+libvirt+virtinstで仮想マシンを作る

自宅環境で仮想マシンVM)を立てるまでです。

必要パッケージのインストール

$ sudo apt install qemu qemu-kvm libvirt-bin virtinst

ディレクトリの整備

/virtual 以下で仮想環境関係のものを管理しようと思っているので、まずはその準備をします。
具体的には以下5つの作業をします。

  1. /virtual などのディレクトリを作る
  2. 仮想環境をいじるグループを作る *1
  3. /virtual上記グループ所有にする
  4. /virtualパーミッションを設定する
  5. アカウントをグループに追加する
$ sudo mkdir /virtual/
$ sudo groupadd virtadmin
$ sudo -R chown :virtadmin /virtual
$ sudo chmod 2770 /virtual
$ mkdir /virtual/{disk,iso}
$ sudo chown root /virtual/{disk,iso}
$ sudo usermod -aG virtadmin <user>
$ sudo usermod -aG virtadmin libvirt-qemu

*2 *3

OSイメージのダウンロード

$ cd /virtual/iso
$ wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-9.3.0-amd64-netinst.iso

VMの作成

次の非常に長いコマンドでVMを作成できます。

$ virt-install --name=first --disk=/virtual/disk/first.qcow2,format=qcow2,size=4 --vcpus=1 --ram=1024 --cdrom=/virtual/iso/debian-9.3.0-amd64-netinst.iso --noautoconsole --graphics vnc,port=5900,listen=127.0.0.1 --network bridge=virbr0,model=virtio --os-type=linux --virt-type=kvm

出力は以下です。

WARNING  /virtual/disk/first.qcow2 may not be accessible by the hypervisor. You will need to grant the 'libvirt-qemu' user search permissions for the following directories: ['/virtual/disk', '/virtual']

Starting install...
Allocating 'first.qcow2'                                                                         | 4.0 GB  00:00:00
Creating domain...                                                                               |    0 B  00:00:00
Domain installation still in progress. You can reconnect to
the console to complete the installation process.

Allocating 'xxxx.qcow2'が出ていて、その後にRemoving disk 'xxxx.qcow2'が出ていなければ上手くいっていることが多い印象です。
WARNING が出てますが、上記libvirt-qemuパーミッションを与えているので問題ないはずです。

VMへの接続

VMの作成でのコマンドでは、debian のディスクからブートしただけなので、debianをイチからインストールしていく必要があります。
インストールするには画面につなぐ必要があり、上記コマンドでは --graphics vnc,port=5900,listen=127.0.0.1VMの画面へのつなぎ方を指定しています。
今回は、VNCで画面が見れるようにしています。

$ ss -tna | grep 5900
LISTEN     0      1      127.0.0.1:5900                     *:*

ローカルループバックIPアドレスからのみを許可しているので、SSHポートフォワード経由でVNC接続すれば画面が見れるので、インストールできます。
(今回はやってませんが。自動インストールしたい。自動インストールしたら画面とか不要じゃない?SSHだけで良くない?)

補足

VMの一覧を見る

現在libvirtdで管理されているVMの一覧はvirsh list --allで確認できます。

$ virsh list --all
 Id    Name                           State
----------------------------------------------------
 3     first                          running

--allを付けているとシャットダウン中のlibvirtdが管理しているVMも表示されます。

libvirtd

ここまで説明ゼロで申し訳ありませんが、libvirt-binをインストールすると、仮想環境の維持管理を行ってくれる libvirtd というデーモンに、 virsh でアクセスして操作するようになります。
QEMU だと本来は qemu-system-x86 ...... のようなコマンドで、VMが終了するまで実行する形ですが、そこをlibvirtdが裏でやってくれる。)

本来 virshVMを作るとけっこうめんどくさい XML を書く必要がありますが、virt-install を使うとコマンドだけでVMを作成できるようになります。 *4
virt-install はブリッジインターフェースなどの設定も勝手にやってくれる(はず)なので非常に便利です。

VMの強制終了

VMを強制終了(いわゆる電プチ)するにはvirsh destroy <domain>します。
<domain>上記virsh listで出てくるName部分です。

*1:お前しか触らんやないかいってツッコミは無粋です

*2:usermod でグループを追加するのはミスりやすいからやめとけって記事がヒットしたりしますが私は知りません

*3:SGID 立ってても root でファイル作ったら引き継がれないんですね。

*4:けっこうめんどくさいXML 第22章 virsh でゲストを管理 - Red Hat Customer Portal

*5:URL長すぎでビビる

自宅仮想環境 - OS インストール

PCが届いたのでOSをインストールしました。

OS

Ubuntu 16.04.3 LTS です(Arch じゃありません。日和りました)。
Download Ubuntu Server | Download | Ubuntu

/etc/fstab で詰まってた話

ネットワーク環境が最悪で、「ベースシステムのインストール」が何回か失敗してました。
フォーマット直後でないディスクにインストールしようとすると「unclean」と怒られますが、その他設定の良い具合のものが見つかったのでクリーンインストールして終わりにしようとしました。
何回かリトライしていて、早く終わらせたかったのでめんどくさいパーティショナーを飛ばそうと、シェルを起動して /dev/sdb? をそれぞれフォーマットしました(USBフラッシュメモリからインストールしたので /dev/sda はそれが占めてます)。

つまり、インストーラのパーティショナーを通らずにベーシックシステムをインストールしました。
すると、/etc/fstabが作られなかったらしく、/ のみがRead Onlyでマウントされた状態で起動しました。

対応として、今回はパーティションは↓を採用していたので、そう/etc/fstabに雑に書きました。(参考: fstab - ArchWiki

  • /dev/sda1 -> /
  • /dev/sda2 -> /boot
  • /dev/sda3 -> /home
  • /dev/sda4 -> /var

上記対応で、(他に不具合ないか怖いですが、)なんとか動いてます。
なんかあったらそれはそれでおいしいとも思ってます。

スペック

ディスクは SSD 120GB が発掘されました。

$ cat /proc/cpuinfo | grep model\ name | head -1
model name      : Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
$ cat /proc/meminfo | grep MemTotal
MemTotal:       32829020 kB
$ sudo fdisk -l /dev/sda | head -n 1
Disk /dev/sda: 111.8 GiB, 120034123776 bytes, 234441648 sectors

最低限のユーティリティのインストール

# apt install vim openssh-server

vim はユーティリティ。

明日以降の予定

  • 物理的な位置を決める
    • SSHサーバーもインストールしたので、もうモニターが必要ない!(フラグ)
  • /etc/fstab をまともにする
    • lsblk -fから拾ってきたUUIDで指定する
  • QEMUlibvirt・virtinstのインストール
    • メイン
  • dnsmasq マシンの作成

第32回シェル芸勉強会 Q1・Q2・Q3 の別解

昨日は家に帰るのが遅かったので休みました。

前置き

毎日更新で解くと、普段のシェル芸勉強会で問題を解いているときと違い、問題に取り組んでいる期間が長く、頭に問題が残っていて解答を思いつくので、それを残しています。

Q1の解答3

$ echo 14679 | sed 's#.*#seq 9 | tr -dc &\\\\n#' | sh
1


4

6
7

9

解説

この解答は最終的にshに投げてます。投げる前の出力(shで実行されるコマンド)は次のようになっています。

$ echo 14679 | sed 's#.*#seq 9 | tr -dc &\\\\n#'
seq 9 | tr -dc 14679\\n

seqの連番出力から、必要な数字だけ残しています。

発送のタネは、 第32回シェル芸勉強会 Q1 の解答 - nogiro_iotaのメモ の解答1で出力した

 1
-2
-3
 4
-5
 6
 7
-8
 9

です。
上記の出力はseqから不要な数字を除くため、diffを使って作っていますが、そもそもtrを使えば、不要な数字だけを消せます。

Q2の解答5

$ echo 14679 | diff -u <(seq 9) <(grep -o .) | sed 1,3d | cat <(echo {a..z}|tr \  \\n) - | awk '/[a-z]/{a[NR]=$0}/^ /{print $0}/^-/{print a[++i]}' | tr -d \
1
a
b
4
c
6
7
d
9

解説

問題を誤読していました。
第32回シェル芸勉強会 Q2 の解答 - nogiro_iotaのメモ では、「Q1の結果から始めてQ2の解答を作れ」だと思って解いていましたが、「Q1と同じ入力から始めてQ2の解答を作れ」でした。

Q1の解答1で不要行を消しているところで、そのままアルファベットを出力しました。
前回の投稿のときには、「連続したアルファベットを出すのが難しい」と言っていましたがbashのブレース展開を使うと簡単に作れました。

Q2の解答6

$ echo 14679 | grep -o . | sed 's#.*#sed &i&#' | paste -d\| -s | sed 's#.*#echo {a..z} | tr \\  \\\\n | &#' | bash | awk '{print}/^9$/{exit}'
1
a
b
4
c
6
7
d
9

解説

アルファベットの連番は簡単に作れますが、それをコマンド中に混ぜ込むのは地味にめんどくさいので、アルファベットの連番に元の出力を混ぜ込むようにしました。
文での説明だとたぶん良くわからないので、bashに投げる前の出力をご覧ください。

echo {a..z} | tr \  \\n | sed 1i1|sed 4i4|sed 6i6|sed 7i7|sed 9i9

Q3の解答2

seq 65536 | factor | awk 'NF==2{print "\\<"$2"/tcp\\>"}' | grep -f - /etc/services

解説

こちらも問題の誤読ですね。。。
詳しくは 第32回シェル芸勉強会 Q3 の解答 - nogiro_iotaのメモ に書いていますがざっくり説明すると、「/etc/servisesから素数のポート番号を抜き出す問題だと思ってシェル芸したが、問題をよく読むとサービス一覧が必要で番号ではなかったため慌てて後ろにつけてごまかした」ため、ワンライナーが右往左往してます。(右往左往しても結果が簡単に出てくるのがワンライナーのいいところといえばそうですが。。。)

上記の解答だと、先にポート番号の上限まで素数を作ることで短くしています。

PlantUMLがすごい

テキストファイルをソースに、UML図を作れるツール。しゅごい。(というお題目で始めたのにどうしてこうなった)

plantuml.com

走れメロス(前20段落)の状態遷移図です。(オチ)

www.dropbox.com

出力用ワンライナー

返ってこなくてもバグではありませんし、JVMの設定などをしてないとそもそも例外が飛びます(全文だとJava以前にDotの実行から返ってこずTimeoutしますが。。。)。

aozora books --title "走れメロス" | jq '.[0].book_id' | xargs aozora content --format html --id | sed '1,/id="contents"/d' | sed '/class="chitsuki_1"/,$d' | sed 's#<ruby><rb>\([^<]*\)</rb><rp>[^<]*</rp><rt>\([^<]*\)</rt><rp>[^<]*</rp></ruby>#\1#g' | sed 's#<[^>]*>##g' | tr -d \\r | sed 's#[ 、「」]##g' | head -n 20 | mecab | sed 's#\t.*##' | grep -v -e EOS -e '^$' | awk 'NR==1{print "(*) \""$0"\""}NR!=1{print "\""p"\" \""$0"\""}{p=$0}END{print "\""$0"\" (*)"}' | awk '{a[$0]++;i[NR]=$0;a1[NR]=$1;a2[NR]=$2}END{for(idx=1;idx<=NR;idx++){print a1[idx]" -->["a[i[idx]]"] "a2[idx]}}' | awk '!a[$0]++' | cat <(echo @startuml) - <(echo @enduml) | plantuml -p > merosu_20.png

解説

青空文庫CLIでアクセスできる aozora-cli を使って、ワンライナー走れメロスの状態遷移図(アクティビティ図)を作ったよ!

インストール

まずはインストール。

sudo apt install plantuml fonts-ricty-diminished

Rictyフォントをインストールしているのは、Windows Subsystem for Linum (Bash on Windows) には、デフォルトだと日本語が入っていないからです。

$ pip3 install aozora-cli
The program 'pip3' is currently not installed. You can install it by typing:
sudo apt install python3-pip

ʅฺ(・ω・。)ʃฺ??

$ apt install python3-pip
......
$ pip3 install aozora-cli
Collecting aozora-cli
  Downloading aozora-cli-0.0.3.tar.gz
Collecting requests (from aozora-cli)
......
aozora books --title "走れメロス" | jq '.[0].book_id' | xargs aozora content --format html --id | sed '1,/id="contents"/d' | sed '/class="chitsuki_1"/,$d' | sed 's#<ruby><rb>\([^<]*\)</rb><rp>[^<]*</rp><rt>\([^<]*\)</rt><rp>[^<]*</rp></ruby>#\1#g' | sed 's#<[^>]*>##g' | tr -d \\r | sed 's#[ 、「」]##g' | head -n 20 | mecab | sed 's#\t.*##' | grep -v -e EOS -e '^$' | awk 'NR==1{print "(*) \""$0"\""}NR!=1{print "\""p"\" \""$0"\""}{p=$0}END{print "\""$0"\" (*)"}' | awk '{a[$0]++;i[NR]=$0;a1[NR]=$1;a2[NR]=$2}END{for(idx=1;idx<=NR;idx++){print a1[idx]" -->["a[i[idx]]"] "a2[idx]}}' | awk '!a[$0]++' | cat <(echo @startuml) - <(echo @enduml) | plantuml -p > merosu_20.png
java.lang.NegativeArraySizeException

(#^ω^)

$ sudo vim `which plantuml`
$ tail -n 1 `which plantuml`
$JAVA -Xmx4096m -jar /usr/share/plantuml/plantuml.jar ${@}

で再度実行したらできます。

まとめ

plantumlもaozora-cli青空文庫もしゅごい。

おまけ

1文

f:id:nogiro_iota:20171214223120p:plain

aozora books --title "走れメロス" | jq '.[0].book_id' | xargs aozora content --format html --id | sed '1,/id="contents"/d' | sed '/class="chitsuki_1"/,$d' | sed 's#<ruby><rb>\([^<]*\)</rb><rp>[^<]*</rp><rt>\([^<]*\)</rt><rp>[^<]*</rp></ruby>#\1#g' | sed 's#<[^>]*>##g' | head -n 1 | tr -d \\r | sed 's#[ 、「」]##g' | sed 's#\([^。]*。\).*#\1#' | mecab | sed 's#\t.*##' | grep -v -e EOS -e '^$' | awk 'NR==1{print "(*) \""$0"\""}NR!=1{print "\""p"\" \""$0"\""}{p=$0}END{print "\""$0"\" (*)"}' | awk '{a[$0]++;i[NR]=$0;a1[NR]=$1;a2[NR]=$2}END{for(idx=1;idx<=NR;idx++){print a1[idx]" -->["a[i[idx]]"] "a2[idx]}}' | awk '!a[$0]++' | cat <(echo @startuml) - <(echo @enduml) | plantuml -p > merosu_1.png
1段落

f:id:nogiro_iota:20171214223104p:plain

aozora books --title "走れメロス" | jq '.[0].book_id' | xargs aozora content --format html --id | sed '1,/id="contents"/d' | sed '/class="chitsuki_1"/,$d' | sed 's#<ruby><rb>\([^<]*\)</rb><rp>[^<]*</rp><rt>\([^<]*\)</rt><rp>[^<]*</rp></ruby>#\1#g' | sed 's#<[^>]*>##g' | head -n 1 | tr -d \\r | sed 's#[ 、「」]##g' | mecab | sed 's#\t.*##' | grep -v -e EOS -e '^$' | awk 'NR==1{print "(*) \""$0"\""}NR!=1{print "\""p"\" \""$0"\""}{p=$0}END{print "\""$0"\" (*)"}' | awk '{a[$0]++;i[NR]=$0;a1[NR]=$1;a2[NR]=$2}END{for(idx=1;idx<=NR;idx++){print a1[idx]" -->["a[i[idx]]"] "a2[idx]}}' | awk '!a[$0]++' | cat <(echo @startuml) - <(echo @enduml) | plantuml -p > merosu.png

https://www.dropbox.com/s/3d5q3ih40v3u4ip/merosu.png?dl=0

整形ワンライナー
aozora books --title "走れメロス" \
  | jq '.[0].book_id' \
  | xargs aozora content --format html --id \
  | sed '1,/id="contents"/d' \
  | sed '/class="chitsuki_1"/,$d' \
  | sed 's#<ruby><rb>\([^<]*\)</rb><rp>[^<]*</rp><rt>\([^<]*\)</rt><rp>[^<]*</rp></ruby>#\1#g' \
  | sed 's#<[^>]*>##g' \
  | tr -d \\r \
  | sed 's#[ 、「」]##g' \
  | head -n 20 \
  | mecab \
  | sed 's#\t.*##' \
  | grep -v -e EOS -e '^$' \
  | awk 'NR==1{print "(*) \""$0"\""}NR!=1{print "\""p"\" \""$0"\""}{p=$0}END{print "\""$0"\" (*)"}' \
  | awk '{a[$0]++;i[NR]=$0;a1[NR]=$1;a2[NR]=$2}END{for(idx=1;idx<=NR;idx++){print a1[idx]" -->["a[i[idx]]"] "a2[idx]}}' \
  | awk '!a[$0]++' \
  | cat <(echo @startuml) - <(echo @enduml) \
  | plantuml -p > merosu_20.png

自宅仮想環境を構築するために、PCをポチったこと

(ほぼタイトルどおりですが。。。)
自宅にQEMU+libvirtの仮想環境を構築するためにPCを買いました。

なぜ今の時代にクラウド環境ではなく自宅に環境を作るのか、という話ですが主に以下の3点が理由です。

  • 別件でQEMU+libvirtを触っているが、 QEMU on VirtualBox で若干重いし、テキトーに使うのもはばかられること
  • 自宅にDNSのリゾルバがほしい
  • 音楽用のサーバーがほしい
  • Raspbian の動く仮想環境がほしい

それで色々と検討した結果、↓をポチりました。

Shuttle 小型ベアボーンPC 200x78x250mm ブラック XH110G

Shuttle 小型ベアボーンPC 200x78x250mm ブラック XH110G

上記の目的に達するには、最低限

  • 仮想環境用なのでVT-xサポートしてるCPU対応のマザーが載ってる
  • サウンドカードを挿せる

という条件がまずあり、そこに、

  • ミニPCに触れてみたい
  • Windowsが載っててもどうせ使わないからディスクのついてないベアボーンがいいかな

という欲求が混ざった結果です。

CPU とメモリは↓です。

ディスクは2.5インチをマウントできるのですが、家にいっぱい転がってるものを適当にピックアップして使う予定です。(たぶん40GBのSSDを使います。)
まだ、注文しただけで、おそらく届くのは土曜日になリます。楽しみですね。

ブログに書いてるのは、家に環境作るのはやりたいことではありますが、やりたいなーと思っててもやらないので、(誰も読んでないとしても)宣言しておくことで逃げ道を塞ぐためです。

第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

第32回シェル芸勉強会 Q7 の解答

前置き

今日は第32回シェル芸勉強会のQ7です。

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

思考を忠実にアウトプットすることを目標にしています。

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

次のファイルについて、次の処理をやってください。

ある数字について、上下左右の数字どれか1つに0が含まれる場合は0、そうでなければ1にする。
次に、上下左右の数字どれか1つに1が含まれる場合は1、そうでなければ0にする。

$ cat image.txt
00010000000000001000000
00000000111111111110000
01111110011111111110001
00011110001111111111000
10011110001111111111000
00000001000000001000000

正解の出力を示します。

00000000000000001000000
00000000001111111100000
00001100011111111110000
00011110001111111111000
00001100000111111110000
00000000000000001000000

問題を見た感想・補足

ライフゲームっぽい。ただ、ワンライナーでやるのはしんどそう。

解答1

cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]&&a[k1+1][k2]&&a[k1][k2-1]&&a[k1][k2+1])}}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]||a[k1+1][k2]||a[k1][k2-1]||a[k1][k2+1])}}}' | sed 's#.*,##' | tr -d \\n | fold -w 23 | awk 1

解説

とりあえず縦横の数を数えます。

$ cat image.txt | wc
      6       6     144
$ cat image.txt | sed 1\!d | grep -o . | wc
     23      23      46

どうやら 6x23のようです。

2次元配列っぽいデータだとワンライナーで処理しづらいので、座標-値に変換します。(以下の例だとCSVライクに出力します)

$ cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | head
1,1,0
1,2,0
1,3,0
1,4,1
1,5,0
1,6,0
1,7,0
1,8,0
1,9,0
1,10,0

で、次の

ある数字について、上下左右の数字どれか1つに0が含まれる場合は0、そうでなければ1にする。

の結果は座標ごとに、
y_{ij} = x_{(i-1)j} \&\& x_{i(j-1)} \&\& x_{(i+1)j} \&\& x_{i(j+1)}
を求めればいいです。

cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]&&a[k1+1][k2]&&a[k1][k2-1]&&a[k1][k2+1])}}}'

ザ・ゴリ押しですが、awkの2次元配列に入れて(結局2次元配列に入れてる。。。)論理積を取ります。
awkは未定義変数(a[0][1]など(1オリジンのため0が未定義))を参照したときに0になるので、この問題としては良くないですが、今回のデータには辺に1が3つ並んでいないので大丈夫です。

最後の

次に、上下左右の数字どれか1つに1が含まれる場合は1、そうでなければ0にする。

も同様にゴリ押しして、整形すれば答えになります。(今回は論理和です。)

$ cat image.txt | sed 's#.#& #g' | awk '{for(i=1;i<=NF;i++){print NR","i","$i}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]&&a[k1+1][k2]&&a[k1][k2-1]&&a[k1][k2+1])}}}' | awk -F, '{a[$1][$2]=$3}END{for(k1 in a){for(k2 in a[k1]){print k1","k2","(a[k1-1][k2]||a[k1+1][k2]||a[k1][k2-1]||a[k1][k2+1])}}}' | sed 's#.*,##' | tr -d \\n | fold -w 23 | awk 1
00000000000000001000000
00000000001111111100000
00001100011111111110000
00011110001111111111000
00001100000111111110000
00000000000000001000000

まとめ

awkによるゴリ押ししか形になりませんでした。
ただ、2次元配列を2回以上パイプするには座標にしておくのが便利でした。

# ホントはtee >(grep -e $((1-1))),$((1)) -e $((1+1))),$((1)) -e $((1))),$((1-1)) -e $((1))),$((1+1)) | cut -d, -f3 ....) | tee ... をブレース展開で大量に作りたかったですが、最初のgrepでブレース展開すると座標に戻すにはどうすればいいかわかりませんでした。。。

感想

xy座標に変換したらイケそうと思ったが、結局思いつかなかった。
awkでゴリ推した他にも、foldマジックナンバーを渡していたり、雑にawk 1をしてるのが少し嫌