「第12回本当は怖くないシェル芸勉強会&第29回本が出たよUSP友の会定例会」に参加してきました
今回は関西から、Twitter参加しました。
- イベント:http://usptomo.doorkeeper.jp/events/12763
- UST:http://www.ustream.tv/channel/%E3%82%B7%E3%82%A7%E3%83%AB%E8%8A%B8
- 問題と解説:http://blog.ueda.asia/?p=3535
- togetter:http://togetter.com/li/701509
模範解答は上記の上田さんのブログの方にあるので、ここでは自分の出した変な回答の解説などを……
Q1. 次のように、画面にバッテンを描いてください。(この出力例の大きさは21×21です。)
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
https://twitter.com/kunst1080/status/495441002060980224
https://twitter.com/kunst1080/status/495458118512283649
考え方は以下のようにしました
- 空白を0、Xを1として数列を作成する
- できあがった数列を文字列置換する
まず前半
$ (jot 9|sort -rn;echo 0;jot 9) 9 8 7 6 5 4 3 2 1 0 1 2 3 4 5 6 7 8 9
9〜0〜9の数列を作成しています。あとでまとめてパイプに渡したいため、両端をカッコでくくっています。
数列を出力するために、FreeBSDなのでjotコマンドを使用していますが、Linux系の人はseqコマンドが使えると思います。どちらもなかったらyesとawkでがんばる感じですかね。
$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' 1000000000 0100000000 0010000000 0001000000 0000100000 0000010000 0000001000 0000000100 0000000010 0000000001 0000000010 0000000100 0000001000 0000010000 0000100000 0001000000 0010000000 0100000000 1000000000
さっきの数列に対して、10のN乗を計算しています。また、awkのprintf関数で左ゼロ埋めをしています。
「X」の左側に見えてきましたね・・・
$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' | xargs -I% echo "echo -n %;echo % | rev" echo -n 1000000000;echo 1000000000 | rev echo -n 0100000000;echo 0100000000 | rev echo -n 0010000000;echo 0010000000 | rev echo -n 0001000000;echo 0001000000 | rev echo -n 0000100000;echo 0000100000 | rev echo -n 0000010000;echo 0000010000 | rev echo -n 0000001000;echo 0000001000 | rev echo -n 0000000100;echo 0000000100 | rev echo -n 0000000010;echo 0000000010 | rev echo -n 0000000001;echo 0000000001 | rev echo -n 0000000010;echo 0000000010 | rev echo -n 0000000100;echo 0000000100 | rev echo -n 0000001000;echo 0000001000 | rev echo -n 0000010000;echo 0000010000 | rev echo -n 0000100000;echo 0000100000 | rev echo -n 0001000000;echo 0001000000 | rev echo -n 0010000000;echo 0010000000 | rev echo -n 0100000000;echo 0100000000 | rev echo -n 1000000000;echo 1000000000 | rev
「さっき作った文字列をrevコマンドで左右反転してくっつけるコマンド」をまず作成しています。paste使ってもよさそうですが…
echo -n は、改行せずにechoするためのオプションです。
$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' | xargs -I% echo "echo -n %;echo % | rev" | sh 10000000000000000001 01000000000000000010 00100000000000000100 00010000000000001000 00001000000000010000 00000100000000100000 00000010000001000000 00000001000010000000 00000000100100000000 00000000011000000000 00000000100100000000 00000001000010000000 00000010000001000000 00000100000000100000 00001000000000010000 00010000000000001000 00100000000000000100 01000000000000000010 10000000000000000001
作成したコマンドをshコマンドに食わせて実行しました。どう見てもXになっています。
あとは、1→X、0→スペース の変換をするだけです。
$ (jot 9|sort -rn;echo 0;jot 9) | awk '{printf "%010d\n", 10^$1}' | xargs -I% echo "echo -n %;echo % | rev" | sh | tr '01' ' X' X X X X X X X X X X X X X X X X X X XX X X X X X X X X X X X X X X X X X X
……なんだか少しお題とは少し違う形をしてますが。。。
trコマンドを使って文字の置換をしました。
tr '01' ' X'
で、'0'を' 'に、'1'が'X'に、まとめて置換できます。
こんな風に、sedを使わなくてもtrコマンドでも置換ができます。
Q4. 北から順(正確には都道府県番号順)に並べてください。
$ cat pref 鹿児島県 青森県 大阪府 群馬県
https://twitter.com/kunst1080/status/495454679942168576
https://twitter.com/kunst1080/status/495455438087794688
WEBの情報を活用して作業をする、という観点の問題でした。
頭を固くせず、使えるものは使いましょうっていうことですね。
curlで取ってきた段階だと文字コードがSJISなので、nkf -w でUTF-8に変換します。
$ curl elze.tanosii.net/d/kenmei.htm -s | nkf -w <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=Shift_JIS"> <META http-equiv="Content-Style-Type" content="text/css"> <TITLE>県番号,都道府県名,県庁所在地一覧</TITLE> </HEAD> <BODY> (以下略
charset=Shift_JISと書いてあり、元々Shift JISだったと思われますが、nkfでUTF-8になりコンソールで見えるようになっています。
で、これにgrepしてほしいデータの行だけを抽出します。ここで
grep -of
を使うとスマートにできます。(@TSB_KZKさんに教えていただきました。)
manによると
- -o, --only-matching…マッチした行の、マッチした部分だけを出力
- -f FILE, --file=FILE…検索パターンをファイルから読み込む
ということです。
繋げると、
$ curl elze.tanosii.net/d/kenmei.htm -s | nkf -w | grep -of pref 青森県 群馬県 大阪府 鹿児島県
Q5. 各行の数字を大きい順にソートしてください。
$ cat input A 31 1234 -42 4 B 10 31.1 -34 94
https://twitter.com/kunst1080/status/495463901664784384
なんぞこれ……
考え方は以下のようにしました
- 1列目の名前で1行のデータを持つ中間ファイルを作成する
- それぞれの中間ファイルをソートしてからくっつける
まずはこのawkの塊ですが、最後のshを外すとこうなります。
$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}END{for(i=1;i<NR+1;i++){print"(echo",f[i],";sort -n",f[i]")|xargs"}}' input echo 31>>A echo 1234>>A echo -42>>A echo 10>>B echo 31.1>>B echo -34>>B (echo A ;sort -n A)|xargs (echo B ;sort -n B)|xargs
END句も外してみて確認です。
$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}' input echo 31>>A echo 1234>>A echo -42>>A echo 10>>B echo 31.1>>B echo -34>>B
「2列目以降の内容を、1列目の名前のファイルに出力するコマンド」が並びます。
またこのとき、連想配列「f[]」に、「A」と「B」(1列目の値)を退避しています。これはEND句で使用します。
END句も合わせると
$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}END{for(i=1;i<NR+1;i++){print"(echo",f[i],";sort -n",f[i]")|xargs"}}' input echo 31>>A echo 1234>>A echo -42>>A echo 10>>B echo 31.1>>B echo -34>>B (echo A ;sort -n A)|xargs (echo B ;sort -n B)|xargs
となり、「2列目以降の内容を、1列目の名前のファイルに出力するコマンド」と、「1列目の名前のファイルをソートしてxargsで横にするコマンド」が出来上がります。
(sort -n は数字としてソートするオプション)
最後に、作成したコマンドをshコマンドに流しこんで実行します
$ awk '{for(i=2;i<NF;i++){f[NR]=$1;print "echo",$i">>"$1}}END{for(i=1;i<NR+1;i++){print"(echo",f[i],";sort -n",f[i]")|xargs"}}' input | sh A -42 31 1234 B -34 10 31.1
Q7. Q6のグラフを次のように縦にしてください。(多少ズレてもよしとします。)
* * * * * * * * * * * * * * * * * * * * * * * * 5 3 4 10 2
※Q6の入力ファイル
$ cat num 5 3 4 10 2
https://twitter.com/kunst1080/status/495469327311593472
※pasteコマンドを使いたかったので、この問題はLinux(ZorinOS)で解きました。
考え方は以下のようにしました
- 1列ずつ出力を作成する(プロセス置換(?)として作成)
- それらをpasteで横に並べる
まず前半、xargsの手前まで
$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" <(yes \"\*\" | head -5;echo 5) <(yes \"\*\" | head -3;echo 3) <(yes \"\*\" | head -4;echo 4) <(yes \"\*\" | head -10;echo 10) <(yes \"\*\" | head -2;echo 2)
xargsの-Iオプションで標準入力を変数化できるので、それを利用して「棒グラフ1本を生成するプロセス置換」を生成します。
試しに1行目のコマンドだけを実行してみるとこんな感じです
$ yes "*" | head -5;echo 5 * * * * * 5
さて、次にこれらをxargsで並べます。
$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" | xargs <(yes "*" | head -5;echo 5) <(yes "*" | head -3;echo 3) <(yes "*" | head -4;echo 4) <(yes "*" | head -10;echo 10) <(yes "*" | head -2;echo 2)
並びましたね。
pasteコマンドに突っ込みたいので、echoで先頭にpasteを付けます。
$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" | xargs echo paste paste <(yes "*" | head -5;echo 5) <(yes "*" | head -3;echo 3) <(yes "*" | head -4;echo 4) <(yes "*" | head -10;echo 10) <(yes "*" | head -2;echo 2)
これで、「『棒グラフを1本ずつ生成するプロセス置換』を横に結合するコマンド」が出来上がりました。
プロセス置換はbashの機能なので、shではなくbashに流し込みます
$ cat num |xargs -I% echo "<(yes \\\"\*\\\" | head -%;echo %)" | xargs echo paste | bash * * * * * * * * * * * * * * 2 * 3 * * * 4 * 5 * * * * * 10
若干変な出力ですが。。。
以上です。
(なんだか今回まともにできてない気がしなくもないですが……)
今回のシェル芸会で、だんだんとpasteコマンドの使用に慣れてきたような感じです。
便利そうなので、もっと手に馴染ませていきたいところ。
次回予告
次回は、大阪で有志を募ってサテライト会場やるかも???