第36回シェル芸勉強会の参加レポートです。
最近あまりシェル芸してないからか、別解を思いつかなかったり、似たような解法を使ったりしてますね・・・・・・
参考リンク等
午前の部の資料
本日のシェル芸勉強会の午前の部の資料のURLです。
— Hidekazu Toriumi (@hid_tori) 2018年7月7日
次回からちゃんと文字コードの話になる予定です。
# シェル芸https://t.co/YIg1k1ntxb
午前の部
午前の部は文字コードと銘打たれていましたが、文字コードの前提となる知識みたいな内容でした。次回に続くそうです。
locale
こういう遊びを知りました。
locale | grep LC | grep -v ALL | cut -d= -f1 | shuf -r | sed 's#.*#echo &; locale -k &; sleep 2; clear#' | sh #シェル芸 これたのしい
— のぎろ (@nogiro_iota) 2018年7月7日
iconv
文字コードを変換する iconv の紹介がありました。
Windows-31J と仲良くするために日常的に iconv -t cp932
を使っている自分には馴染み深いコマンド。
TL を眺めていると、文字コードには nkf を使ってる人が多いみたいでした。
バイナリをテキストに変換する方法
文字コードはもちろんバイナリなので、シェル芸で扱うにはテキスト化する必要があります。
講師の鳥海さんは od や hexdump も紹介されておられましたが、正味ふだん使っている xxd
しか覚えてません・・・・・・
xxd -ps
と xxd -r -p
が逆変換になってくれてるのが個人的にすごい嬉しい。
基数変換
bc の基数変換は私は良く使ってますが、その他に bash 組み込みの $(())
でも基数変換できるそうです。
$ echo $((25#1000)) 15625
echo 基数変換一覧 #シェル芸
— のぎろ (@nogiro_iota) 2018年7月7日
printf '%x' {32..126} | xxd -r -p | grep -o . | grep -v -e \" -e "'" -e \\[ -e \\] -e \` -e \\ | sed 's%.*%echo '\''& => '\''$((64#&))%' | bash 2> /dev/null | sort -n -k 3
私の問題と解答
Q1 は全然わからなくて断念しました。
また、Q8 も途中まではいい感じでしたが結局最後までは行けず・・・
Q1
welcome.txt
に隠されたメッセージを読み取ってください。また、welcome.txt
をワンライナーで作ってみてください。
A1 (断念)
cat welcome.txt | xxd -ps | tr -d \\n | fold -w 84 | awk 1
解説
cat で見ると、アンダースコアしか存在しない不思議なファイルだったので、とりあえずxxd -ps
してみました。すると 0x5f と 0x00 からなるファイルだとわかったので、幅を調整すれば絵が出そうだと当たりを付けました。
最終行まで幅がすべて同じである保証はありませんが、揃っている方がきれいだと思ったので、揃う幅(因数)を知るために factor を通しました。
$ cat welcome.txt | wc -c | factor 1680: 2 2 2 2 3 5 7
3*7 の倍数を中心に追ってる間に時間切れ。
感想
toilet の出力が幅 70 文字だから幅は 70文字ということでした。
figlet も toilet もあまりちゃんと使ったことがないのが仇になりました。
Q2
次のファイル群について、全てファイル名を
N年M組.doc
(N
は半角数字、M
は半角大文字)に揃えて徳を積んでください。$ ls 1-B.doc 3年C組.doc 3年A組.doc 1ーC.doc 4年C組.doc 1A.doc 4年a組.doc 3年B組.doc 1ーD.doc 3年D組.doc 5年A組.doc 4年B組.doc 1年E組.docこれ最悪じゃないですか pic.twitter.com/O2ezYAFMMK
— 漁師 (@6Lgug) 2018年6月26日
A2
cd worst/ && ls | sed p | sed '0~2y/1234ABCDEFal/1234ABCDEFA1/' | sed '0~2s#[^1-5A-F]##g' | sed '0~2s#\(.\)\(.\)#\1年\2組.doc#' | paste -d\ - - | sed 's#.*#mv &#' | sh
https://twitter.com/nogiro_iota/status/1015453781394223104
解説
「| sh
に投げれば何でもできる」系の解き方をしてます。というか
mv
のような破壊的な処理を扱うときは、最終的に実行されるスクリプトを眺めるために、 | sh
に投げれる形に積極的にしたいですね。今回は汚い元データとキレイになりつつある成形中データを同一のパイプラインに流して処理しています。
やり方としてはすべて sed
で偶数行だけ (0~2
) 処理したあとに paste - -
で横に並べています。
キレイなデータを作る部分は概ね sed
の y
コマンドで置き換えています。
マルチバイト文字が含まれていると tr
でできないのがちょっと面倒ですね・・・・・・
感想
いま考えると 1 B
のように空白の入っているファイル名があったらちゃんとできてないですね。
そういう場合だったとすると paste
の前に sed 's%.*%"&"%'
を挟むとうまくいきます。
Q3
2018年のすべての日付について、2,3,5,7が4つ含まれる日付を列挙してください(例: 2018年3月22日など)。
A3.1
echo 2018{01..12}{01..31} | tr \ \\n | xargs -n 1 date +%Y%m%d -d 2> /dev/null | sed p | sed '0~2s%[^2357]%%g' | awk 'length($0)==4{print p}{p=$0}'
https://twitter.com/nogiro_iota/status/1015457900951568384
解説
日付を列挙するのにはイディオムがあって以下のコマンドで出ます。echo 2018{01..12}{01..31} | tr \ \\n | xargs -n 1 date +%Y%m%d -d 2> /dev/null
2,3,5,7 の抽出部分は Q2 と似たようなやり方を使っています。
事前に sed p
で行を 2 つにしておいて、0~2
で偶数(後ろの方の)行だけから 2,3,5,7 を抽出します。
そうすると文字幅が 4 である行の前の行が答えになるので、 awk
で抜き出しています。
A3.2
echo 2018{01..12}{01..31} | tr \ \\n | xargs -n 1 date +%Y%m%d -d 2> /dev/null | python3 -c 'import functools;any(print(i) if(functools.reduce(lambda a,b:a+i.count(b), ["2","3","5","7"], 0) == 4)else 0 for i in (input() for null in iter(int, 1)))'
https://twitter.com/nogiro_iota/status/1015466634222292992
解説
Python ワンライナーが得意そうな問題だったのでやったらできました。3 項演算子で、入力文字列中の 2,3,5,7 の数の和が 4 だったら出力し、4でなかったら何もしないという分岐を行っています。
Python のリスト内包表記ワンライナーらしさが出ています。
reduce()
を使わず i.count("2") + i.count("3") + ...
とやると Twitter の文字制限に引っかかったので、重複部分を削るのはやっぱり大事です。
※ 執筆中に reduce()
を sum(map())
に置き換えたほうが見やすいし、import もなくなるので圧倒的に短いことに気づきましたが・・・
functools.reduce(lambda a,b:a+i.count(b), ["2","3","5","7"], 0) == 4
↓
sum(map(lambda a:i.count(a), ["2","3","5","7"])) == 4
感想
dateutils という日付を扱うためのコマンドがあるらしく、イディオム的に出力処理を作った日付部分を代替できるみたいです。
他にも日付の足し算などもできるみたいで普通に便利そう。
Q4
俳句を考え、次の短冊に縦書きで入れてください。
$ cat tanzaku ┏ ーー-┷-ーー┓ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗ーーーーーー┛
A4
echo あいうえお かきくけこさし すせそたち | grep -o . | awk 'BEGIN{r=1;c=6}/ /{--c;r=1}!/ /{print ++r"s%.%"$0"%"c}' | sed -f - tanzaku
https://twitter.com/nogiro_iota/status/1015462783352107008
解説
最後のsed -f - tanzaku
に渡す前の出力を見れば目指すところがわかりやすいかと思います。
$ echo あいうえお かきくけこさし すせそたち | grep -o . | awk 'BEGIN{r=1;c=6}/ /{--c;r=1}!/ /{print ++r"s%.%"$0"%"c}' | head -n 6 2s%.%あ%6 3s%.%い%6 4s%.%う%6 5s%.%え%6 6s%.%お%6 2s%.%か%5
(17行は長いので列の変わる 6 行目まで出力しています。)
これがなんなのかは下の感想をご確認ください。(感想ってなんだ???)
感想
これはもう、昔やったことあるから解けたみたいな感じです。
昔下のようなビットマスクの 2 次元版みたいな処理がしたかったときがあって、
下記のようになる command
— のぎろ (@nogiro_iota) 2015年11月25日
$ cat file1
abcd
efgh
$ cat file2
0010
1100
$ command file1 file2
$ cat file1
00c0
ef00
(2 回めの
cat file1
はコピペミスです)下のようなことをしてました。
できた
— のぎろ (@nogiro_iota) 2015年11月25日
$ echo -e 'abcd\nefgh' | sed -f <(echo -e '0010\n1010' | sed 's/./& /g' | awk '{for (i=1;i<=NF;i++){if ($i=="0"){print NR"s_._0_"i}}}')
見やすくすると下のようになります。
$ cat a abcd efgh $ echo -e '0010\n1010' | sed 's/./& /g' | awk '{for (i=1;i<=NF;i++){if ($i=="0"){print NR"s_._0_"i}}}' | sed -f - a 00c0 e0g0
最後の sed -f - a
をなくしてみるとわかるのですが、
$ echo -e '0010\n1010' | sed 's/./& /g' | awk '{for (i=1;i<=NF;i++){if ($i=="0"){print NR"s_._0_"i}}}' 1s_._0_1 1s_._0_2 1s_._0_4 2s_._0_2 2s_._0_4
これは、一番左の数字が縦軸で、一番右の数字が横軸の文字を 0 に置き換える sed コマンドを生成しています。
今回は tanzaku
ファイルに文字の埋まったデータがあるので、この縦軸・横軸を入れ替えてやれば tateyoko
コマンドみたいなことができるわけになります。
それはそれとして until 検索が便利ですね。
from:nogiro_iota * until:2015-11-26 - Twitter Search
Q5
cowsayの牛を右向きにして吹き出しの位置を調整して下さい。(万が一右向きオプションがあったら、それは使わずにお願いします。)
- 例
________________________________ < あなたとJava今すぐダウンロード > -------------------------------- ^__^ / _______/(oo) / /\/( /(__) | w----|| || ||
A5
cowsay あなたとjava今すぐ | sed 's#$# #' | sed 's#^\(.\{30\}\).*#\1#' | rev | tr '\\/()' '/\\)(' | awk 'NR!=2{print}NR==2{for(i=length;i>0;i--){printf(substr($0,i,1))}print ""}' | sed '2s%\(.*>\)\( *\)$%\2\1%'
https://twitter.com/nogiro_iota/status/1015472438652440576
解説
まず、後ろをパディングして、幅を揃えてから反転します。それだけだとメッセージ部分も反転してしまうので、メッセージ部分を別個に反転しなおして、空白を追加した分吹き出しがずれるので移動しています。
感想
sed
の e
コマンドを使えばスッキリ書けたと思うのですが、書き方がわからず・・・・・・
メッセージ部分の空白が、全角文字でズレてるのが若干嫌ですね。
Q6
seq 20の出力について、次のように素数を丸囲みしてください。
1 ② ③ 4 ⑤ 6 ⑦ 8 9 10 ⑪ 12 ⑬ 14 15 16 ⑰ 18 ⑲ 20
A6
seq 20 | factor | awk '{gsub(":","",$1)}NF==2{print "echo '\''print \"246\";"$1"'\'' | cat <(echo obase=16) - | bc | sed '\''s%^2461%247%'\'' | xxd -r -p | iconv -f utf16be | awk 1"}NF!=2{print "echo "$1}' | bash
https://twitter.com/nogiro_iota/status/1015480448590360576
解説
① の Unicode コードポイントを見ると、 0x2461 (間違ってます。正しくは 0x2460)だったので、16 進数にに変換してから 246 を前に付ければ望む結果が得られます。ただし、16 以上の数は桁上りがあるので、 5 桁になっているものは置換して対応しました。
UTF-16 エンコードのときにサロゲートペアを含まない Unicode は iconv で utf16be にエンコード/デコードすると xxd で変換/逆変換したときに扱い易いです。
感想
最初は Unicode の結合文字である U+20DD (前の文字を丸囲いする文字)をつければいいかと思い、それがどのように見えるか確認しました。
echo -n 1 | iconv -t utf16be | xxd -ps | fold -w 4 | sed 's#$#20dd#' | xxd -r -p | iconv -f utf16be #シェル芸 # てすと
— のぎろ (@nogiro_iota) 2018年7月7日
しかし、環境によるのかも知れませんが別々の文字として表示され ② などにならなかったので別のアプローチにしました。
いま考えると、もしできていたとしても ⑪ などの 2 桁のものは 1① になってしまっていたということになるので、どのみち正解にはなりませんでした。
あと、① → 0x2460 で 0x2461 ではなかったために 1 ズレてしまっていました。
正しい解は下になります。
seq 20 | factor | awk '{gsub(":","",$1)}NF==2{$1=$1"-1";print "echo '\''print \"246\";"$1"'\'' | cat <(echo obase=16) - | bc | sed '\''s%^2461\\([0-9]\\)%247\\1%'\'' | xxd -r -p | iconv -f utf16be | awk 1"}NF!=2{print "echo "$1}' | bash
Q7
text
には、文字や空白、改行として認識されないバイナリが含まれています。どの行にどんなものがあるか調査してください。
A7
cat text | iconv -t utf16be | xxd -ps | fold -w 4 | grep ^0 | tr -d \\n | sed 's#000a#\n#g' | nl -nln -ba
https://twitter.com/nogiro_iota/status/1015483095007367169
解説
Unicode コードポイントに変換しながら見ていくと、grep -v ^3
などをして普通に見える文字を削っていった結果、残ったのが grep ^0
でした。
感想
こんなことをのたまってましたが、tr はマルチバイト文字を扱えないので、制御文字もマルチバイト文字も一緒くたに扱っているだけですね。
cat で見えてるんだけど、印字可能扱いらしい #シェル芸 pic.twitter.com/ClICyLsGf5
— のぎろ (@nogiro_iota) 2018年7月7日
sed
でやれば普通に出てきました。
$ cat text | sed 's/[[:print:]]//g' | xxd -ps 000a0a0a0a020a0a0a060a0a160a0a0a0a0a0a0a0a
Q8
$ echo 嘘は嘘であると見抜ける人でないと(掲示板を使うのは)難しいから始めて、出力で次のようにルビを打ってください。多少ずれたりスペースが入っても構いません。
ウソ ウソ ミヌ ヒト ケイジバン ツカ ムズカ 嘘は嘘であると見抜ける人でないと(掲示板 を使うのは)難しい
A8 (途中)
echo 嘘は嘘であると見抜ける人でないと(掲示板を使うのは)難しい | pee cat 'mecab | sed '\''/^[はであとでなをの]/!s#.*,##'\'' | sed '\''s%[[:space:]].*%%'\'' | sed '\''/^[はであとでなをの()]/s#.# #g'\'' | sed \$d | tr -d \\n | awk 1' | tac
感想
全角カタカナを半角カタカナにしたりする nkf
のオプションが覚えきれない。。。と思ってるうちに終わっていました。
元の文と揃える方法は全く考えずに進めていて、column -t
も初めて見るものだったので色々新鮮でした。
これ楽しいですね。
$ echo これは、一番左の数字が縦軸で、一番右の数字が横軸の文字を 0 に置き換える sed コマンドを生成しています。 | mecab | sed 's#,[^,]*$##' | sed 's#\t.*,# #' | nkf --hiragana | sed 's%\(.*\) \(.*\)\1%\1 \2%' | sed \$d | awk 'NF==1{$2="*"}{a1[NR]=$1;a2[NR]=$2}END{for(i=1;i<=NR;++i){printf a1[i]" "}print "";for(i=1;i<=NR;++i){printf a2[i]" "}print ""}' | pee 'nkf --katakana | nkf -Z4' cat | sed '2,3!d' | column -t | sed 's% \([^ ]\)%\1%g' | sed 's%\*% %g' イチバンヒダリ スウジ タテジク イチバンミギ スウジ ヨコジク モジ オキカ セイセイ これは、一番 左 の数字が縦軸 で、一番 右 の数字が横軸 の文字を0に置き換えるsedこまんどを生成しています。