第41回シェル芸勉強会の参加レポートです。
だいぶ久々に参加させていただいたように思います。
参考リンク等
午前の部の資料
本日使用した資料はこちらです。
— Hidekazu Toriumi (@hid_tori) 2019年4月27日
全6回にもなってしまった文字コードの勉強会にお付き合い下さり、ありがとうございました。https://t.co/v4kBvnZLSk#シェル芸
午前の部
ついに文字コードのお話が最終回になりました。今回は主に Unicode の結合文字列の話でした。
結合文字列をざっくり言うと、 Unicode 上では同じ「ポ」を表すにも以下の 2 通りの方法があるということです。
- ポ: U+30DD
- ホ + ゜(半濁点): U+30DB U+309A
また、上の単一のコードでひとつの文字になる表現を合成済み文字、下の複数コードの組み合わせで表現するものを結合文字列と呼びます。
同一文書中に符号化方式が混在していると不便なので統一することを Unicode 正規化といい、合成済み文字に統一した形式を NFC、結合文字列に分解した形式を NFD と言います。
CLI で Unicode 正規化するには uconv が便利 (uconv -x NFD
・uconv -x NFC
) ということを知りました。
$ echo -n ポ | uconv -x NFC | iconv -t ucs-2be | xxd -ps 30dd $ echo -n ポ | uconv -x NFD | iconv -t ucs-2be | xxd -ps 30db309a
余談ですが結合文字列といえば Mac OS X がめんどくさい印象です。Windows から OS X に濁点 (・半濁点) がファイル名に入ったファイルを送って、編集して送り返してもらうと濁点が分割されます。
メールなどであれば、おそらくファイル名を変更してやり取りするのでまだ良いですが、DropBox などのファイル同期を取るソフトウェアで OS が混在すると同名のファイルが 2 つできるという悪夢が発生します。
しかも保存しないと増えないのでタイミングがよくわからなくなって原因究明が大変です。
問題と私の回答
私の回答のうち、 Twitter のものはその場での回答です。
pre タグで囲まれている部分は記事執筆の際に WSL で再度実行しています。
Q1
次のファイルについて、2列目をキーにしてエクセルの横列の記号(A, B, ..., Z, AA, AB, ...のやつ)順に並べ替えてください。
$ cat excel 114514 B 1192296 AA 593195 CEZ 4120 TZ 999 QQQ
A1
# A1 awk 縛り
— のぎろ (@nogiro_iota) 2019年4月27日
cat ShellGeiData/vol.41/excel | xargs printf '%s %4s\n' | tr \ % | sed 's#%%# %#' | sort -k2 | tr -d % #シェル芸
$ cat ShellGeiData/vol.41/excel | xargs printf '%s %4s\n' | tr \ % | sed 's#%%# %#' | sort -k2 | tr -d % 114514 B 593195 AA 4120 TZ 1192296 CEZ 999 QQQ
解説
列番号の桁数を合わせてソートしたらできました。他の方の回答を見ていると、 awk の length 関数で列番号の桁数を求める解法が多かったみたいですね。
Q2
次のファイルのレコードを干支順にソートしてください。
$ cat eto_yomi 申 さる 子 ね 寅 とら 卯 う 巳 み 辰 たつ 丑 うし 酉 とり 戌 いぬ 亥 い 午 うま 未 ひつじただし、次のファイルを補助に使って良いこととします。
$ cat eto 子丑寅卯辰巳午未申酉戌亥
A2.1
# A2
— のぎろ (@nogiro_iota) 2019年4月27日
cat ShellGeiData/vol.41/eto | grep -o . | sed 's#.#grep & ShellGeiData/vol.41/eto_yomi#e' #シェル芸
$ cat ShellGeiData/vol.41/eto | grep -o . | sed 's#.#grep & ShellGeiData/vol.41/eto_yomi#e' 子 ね 丑 うし 寅 とら 卯 う 辰 たつ 巳 み 午 うま 未 ひつじ 申 さる 酉 とり 戌 いぬ 亥 い
解説
sed の e を外すとなにをしたかわかるかと思います。$ cat ShellGeiData/vol.41/eto | grep -o . | sed 's#.#grep & ShellGeiData/vol.41/eto_yomi#' grep 子 ShellGeiData/vol.41/eto_yomi grep 丑 ShellGeiData/vol.41/eto_yomi grep 寅 ShellGeiData/vol.41/eto_yomi grep 卯 ShellGeiData/vol.41/eto_yomi grep 辰 ShellGeiData/vol.41/eto_yomi grep 巳 ShellGeiData/vol.41/eto_yomi grep 午 ShellGeiData/vol.41/eto_yomi grep 未 ShellGeiData/vol.41/eto_yomi grep 申 ShellGeiData/vol.41/eto_yomi grep 酉 ShellGeiData/vol.41/eto_yomi grep 戌 ShellGeiData/vol.41/eto_yomi grep 亥 ShellGeiData/vol.41/eto_yomi
A2.2
# A2.2 亜種 grep 縛り
— のぎろ (@nogiro_iota) 2019年4月27日
cat ShellGeiData/vol.41/eto_yomi | awk '{print "/"$1"/s_.*_"$0"_"}' | sed -f - <(sed 's_._&\n_g' ShellGeiData/vol.41/eto) #シェル芸
$ cat ShellGeiData/vol.41/eto_yomi | awk '{print "/"$1"/s_.*_"$0"_"}' | sed -f - <(sed 's_._&\n_g' ShellGeiData/vol.41/eto) 子 ね 丑 うし 寅 とら 卯 う 辰 たつ 巳 み 午 うま 未 ひつじ 申 さる 酉 とり 戌 いぬ 亥 い
解説
grep
を使わないことを目標にしました。
感想
以下のようにしたら grep -o .
みたいなことしてる部分も消せますね。
$ cat ShellGeiData/vol.41/eto_yomi | awk '{print "s_"$1"_"$0" _"}' | sed -f - ShellGeiData/vol.41/eto | xargs -n 2 子 ね 丑 うし 寅 とら 卯 う 辰 たつ 巳 み 午 うま 未 ひつじ 申 さる 酉 とり 戌 いぬ 亥 い
A2.1 の sed 部分を xargs でやるのが方がいました。そっちのほうがスマートですね。
Q3
次のファイルのレコードを数字(第一フィールドの計算結果)が小さい順に並べてください。
$ cat kim_calc 1+2+4 金正日 4*3 金正男 3-1-5 金日成 495/3 金正恩 0x1F 金正哲
A3
# A3
— のぎろ (@nogiro_iota) April 27, 2019
cat ShellGeiData/vol.41/kim_calc | sed 's_\(.*\) \(.*\)_echo $((\1)) \1 \2_' | sh | sort -n #シェル芸
$ cat ShellGeiData/vol.41/kim_calc | sed 's_\(.*\) \(.*\)_echo $((\1)) \1 \2_e' | sort -n | cut -d\ -f2-3 3-1-5 金日成 1+2+4 金正日 4*3 金正男 0x1F 金正哲 495/3 金正恩
解説
まずbc
で計算しようとしたところ0x1F
が変換できませんでした。
$ cat ShellGeiData/vol.41/kim_calc | cut -d\ -f1 | bc 7 12 -3 165 (standard_in) 5: syntax error
bash で式評価すれば 0x 形式を変換できたのでそうしました。
感想
printf でも 0x の変換はできますが計算はできないので、今回の問題では手順が増えますね。
$ printf %d\\n 0x1F 31
Q4
次のファイルはシフトJISのテキストですが、これを1) 辞書順、2) 数字の小さい順、にソートしてください。出力もシフトJISとします。
$ cat sjis | nkf -g Shift_JIS $ cat sjis | nkf -wLux 123 ずんごるももう 31 こきたてひーひー 9 ほじぱんふんじこみ 2242 たまもとやろう
A4-1
# A4 1
— のぎろ (@nogiro_iota) April 27, 2019
cat ShellGeiData/vol.41/sjis | iconv -f cp932 | sort -k2 | iconv -t cp932 #シェル芸
A4-2
# A4 2
— のぎろ (@nogiro_iota) April 27, 2019
cat ShellGeiData/vol.41/sjis | iconv -f cp932 | sed 'y/01234/01234/' | sort -n | sed 'y/01234/01234/' | iconv -t cp932 #シェル芸
$ cat ShellGeiData/vol.41/sjis | iconv -f cp932 | sed 'y/01234/01234/' | sort -n | sed 'y/01234/01234/' 20 ほじぱんふんじこみ 31 こきたてひーひー 123 ずんごるももう 2242 うえってきたかるとらまん
解説
cp932 はMicrosoftコードページ932 - Wikipedia のことで、 Windows の Shift JIS 拡張コードです。
Shift JIS と言ってるときは、ほとんどの場合「Windows で作られた」「Windows で表示したい」ことを指してるので cp932 を指定するのが無難だと思っています。
Q5
サイズの小さい順にソートしてください。
$ cat size 2GB 1.2GB 40000MB 1000000000kB 0.4GB 410MB
A5
# A5.1 計算列消すの忘れててすみません
— のぎろ (@nogiro_iota) April 27, 2019
cat ShellGeiData/vol.41/size | sed p | sed '2~2s_.*_echo & | tr -d B | tr k K | numfmt --from=si_e' | paste - - | sort -k2 -n | cut -f1 #シェル芸
$ cat ShellGeiData/vol.41/size | sed p | sed '2~2s_.*_echo & | tr -d B | tr k K | numfmt --from=si_e' | paste - - | sort -k2 -n | cut -f1 0.4GB 410MB 1.2GB 2GB 40000MB 1000000000kB
感想
以下のツイートを見てnumfmt
を知ったのでそのまま使ったら解けました。
numfmtでもとに戻すのがいいか…? #シェル芸
— Blacknon@通勤電車リハビリ中 (@blacknon_) April 27, 2019
Q6
sleep
と内部コマンドだけを使って次の数を小さい順にソートしてください。$ cat nums 5.4 0.34 2.3 0.9 6
A6
# A6 cat 消した版
— のぎろ (@nogiro_iota) April 27, 2019
(while read a; do (sleep $a; echo $a) & done) < ShellGeiData/vol.41/nums #シェル芸
$ (while read a; do (sleep $a; echo $a) & done) < ShellGeiData/vol.41/nums $ 0.34 0.9 2.3 5.4 6
解説
スリープソートです。スリープソートはバケットとしてタイムスロットを使うバケットソートの一種と見なせるそうです。バケットソート - Wikipedia
非同期で動くので表示がずれるのが気になる場合はwait
をかませば良いです。() でサブシェル内で実行すればジョブ開始のメッセージも出ないのでより見やすくなります。
$ (while read a; do (sleep $a; echo $a) & done && wait) < ShellGeiData/vol.41/nums 0.34 0.9 2.3 5.4 6
感想
sleep
とソートを見た瞬間スリープソートだと思いました。
Q7
次のローマ数字をソートしてください。
$ cat roman IV XI LXXXIX IX XLIII XX VIII
A7
# A7 ごり押し版
— のぎろ (@nogiro_iota) April 27, 2019
cat ShellGeiData/vol.41/roman | sed -E 'p;s_I([XV])_(-1)\1_;s_X([LC])_(-10)\1_;s_I_(1)_g;s_V_(5)_g;s_X_(10)_g;s_L_(50)_g' | sed 's_)(_)+(_g; 2~2s_.*_bc <<< "&"_e' | paste - - | sort -k2 -n #シェル芸
$ cat ShellGeiData/vol.41/roman | sed -E 'p;s_I([XV])_(-1)\1_;s_X([LC])_(-10)\1_;s_I_(1)_g;s_V_(5)_g;s_X_(10)_g;s_L_(50)_g' | sed 's_)(_)+(_g; 2~2s_.*_echo "&" | bc_e' | paste - - | sort -k2 -n | cut -f1 IV VIII IX XI XX XLIII LXXXIX
解説
アラビア数字への変換は以下の手順で行いました。- 減算則の部分 (
IV
・XL
など) を先に負の値に置き換える - 他の記号を対応する正の値に置き換える
- 総和を求める式に変形する
- 計算する
上記手順を偶数列で行って、paste - -
で行を揃えてソートする。という流れですね。
今見ると sed の s_I([XV])_(-1)\1_;s_X([LC])_(-10)\1_
部分を s_I[XV]_(-1)*&_;s_X[LC]_(-1)*&_;
に置き換えたいですね。つまり、負の値そのものに置き換えるのではなく -1 をかけるのでもできます。
動作としては、以下の途中結果がわかりやすいかと思います。
$ cat ShellGeiData/vol.41/roman | sed 'p;s_I[XV]_(-1)*&_;s_X[LC]_(-1)*&_;s_I_(1)_g;s_V_(5)_g;s_X_(10)_g;s_L_(50)_g' | sed 's_)(_)+(_g; 2~2s_.*_echo "&" | bc_' IV echo "(-1)*(1)+(5)" | bc XI echo "(10)+(1)" | bc LXXXIX echo "(50)+(10)+(10)+(10)+(-1)*(1)+(10)" | bc IX echo "(-1)*(1)+(10)" | bc XLIII echo "(-1)*(10)+(50)+(1)+(1)+(1)" | bc XX echo "(10)+(10)" | bc VIII echo "(5)+(1)+(1)+(1)" | bc
感想
手元では動いていたのですがシェル芸 bot では動いていませんでした。
Ubuntu の sh が dash だったのが動かなかった理由だったようです。
Ubuntuの/bin/shはdashなんですよ
— eban (@eban) May 4, 2019
ls -l /bin/sh#シェル芸
numconv
がすごい便利そうでした。珍しく AUR になかったのでその場では試せませんでした。 (AUR: Arch Linux の半公式ユーザーリポジトリ。ソースからビルドしたり、.deb
を展開してインストールするエキセントリックなパッケージがあったりする。)
numconv
だと以下のように解けます。
$ cat ShellGeiData/vol.41/roman | sed 'p; s_.*_echo & | numconv_e' | paste - - | sort -k2n | cut -f1 IV VIII IX XI XX XLIII LXXXIX
Q8
次のファイルを辞書順にソートしてください。ただし、濁点がついているものが先に来るようにしてください。できる人はワンライナー中で「かきくけこがぎぐげご」の文字を使わないでください。
$ cat gagigugego かき氷 ぎ・おなら吸い込み隊 きつねうどん ぐりこもりなが事件 きききりん がきの使い くその役にも立たない げんしりょく発電 ごりらいも こじんてきにはクソ
- 例
がきの使い かき氷 ぎ・おなら吸い込み隊 きききりん きつねうどん ぐりこもりなが事件 くその役にも立たない げんしりょく発電 ごりらいも こじんてきにはクソ
A8
# A8 できた
— のぎろ (@nogiro_iota) April 27, 2019
cat ShellGeiData/vol.41/gagigugego | uconv -x NFD | iconv -t utf16be | xxd -ps | tr -d \\n | sed 's_000a_&\n_g' | sed 's_...._& _g' | sed 's_ 3099 _ 13099 _g' | sort | sed 's_ 13099 _3099_g' | xxd -r -p | iconv -f utf16be #シェル芸
$ cat ShellGeiData/vol.41/gagigugego | uconv -x NFD | iconv -t utf16be | xxd -ps | tr -d \\n | sed 's_000a_&\n_g' | sed 's_...._& _g' | sed 's_3099 _-&_g' | sort -n | sed 's_-3099_3099_g' | xxd -r -p | iconv -f utf16be | uconv -x NFC がきの使い かき氷 ぎ・おなら吸い込み隊 きききりん きつねうどん ぐりこもりなが事件 くその役にも立たない げんしりょく発電 ごりらいも こじんてきにはクソ
解説
手順は以下です。- NFD にする (結合文字列に分解する)
- UTF-16 のビッグエンディアンに変換する
- 16 進表現に変換する
- 濁点 (UTF-16 では 0x3099) をソートで上位に並ぶように置き換える (
3099
->-3099
) - ソートする
- 濁点を戻したり (
-3099
->3099
) など、いろいろ逆手順で戻すuconv -x NFD | iconv -t utf16be | xxd -ps | ... | sed 's_3099 _-&_g'
->sed 's_-3099_3099_g' | xxd -r -p | iconv -f utf16be | uconv -x NFC
その場では -3099
ではなく 13099
に置き換えていましたが、U+0xxx の文字が 2 文字目にあると期待どおりにソートされないので -3099
のほうが汎用的です。
感想
午前の部が活きそう #シェル芸
— のぎろ (@nogiro_iota) April 27, 2019
ってツイートを悠長に打ってる間に答え
cat ./ShellGeiData/vol.41/gagigugego|uconv -x NFD|sort#シェル芸
— たいちょー (@xztaityozx_001) April 27, 2019
が出るという不思議体験を味わいました。
U+3099(゛) が先にソートされる理由がよくわかりませんが・・・・・・
総括
久々の参加だったのですが、今回は比較的簡単で良かったです。(スリープソート以外は「これを知ってないと解けない」問題がないので。)
簡単でしたがそれでも疲れるのと、相変わらず全然知らないこと (uconv
・numfmt
・numconv
など) が出てくるのがすごいです。
あと、もとの表現を残したまま計算したいときに sed 'p;s/.*/echo & | hoge/e' | paste - -
のパターンが使えますね。A5・A7で使用しています。