jus & USP友の会共催 シェルワンライナー勉強会@関西(第11回シェル芸勉強会) に参加してきました。

USP の勉強会には初参加でしたが、楽しかったです。

今回の問題文は 20140614 【問題だけスライド】jus & USP友の会共催 シェルワンライナー勉強会@関西(第11回シェル芸勉強会) で公開されています。

準備

持ち物のところで「open usp Tukubai の入ったUNIX/Linux環境のあるノートPC(Macでも可)」とあったので、 https://github.com/usp-engineers-community/Open-usp-Tukubai をインストールした Linux 環境を用意したのと、 Tukubai on FreeBSDダウンロード から ova ファイルをダウンロードして VirtualBox にインポートして用意しておきました。

結局 open usp Tukubai は不要でしたが、 用意していた Linux 環境は動作確認に使いました。

ソフトウェアツールとAWK・sedについて座学

最初の「ソフトウェアツールとAWK・sedについて座学」の話からのメモです。

  • フルスクラッチから1日でCMSを作る シェルスクリプト高速開発手法入門 という本が出ますという話
  • IEEE から出ている Netizens という本からの話
    • ed コマンド, grep コマンド
    • パイプとフィルタコマンド
    • sed, awk
    • 現在の GNU coreutils に入っているようなコマンド群
  • 実際にコマンドを実行してみる話
    • echo です。ます。でした。 | sed 's/。/&\n/g'
      • OSX だと echo です。ます。でした。 | sed 's/。/&\本当に改行/g' でOK (これは Linux でも OK)
      • bash や zsh なら sed $'s/。/&\\\n/g' で本当の改行の代わりに $'\n' でも大丈夫
  • 縦と横の話
    • echo {1..10} | awk '{for(i=1;i<=NF;i++){a+=$i};print a}'
    • echo {1..10} | tr ' ' '\n' | awk '{a+=$1}END{print a}'
  • 複雑な例 sm2 というのは tukubai のコマンドだが今回はなくても良い
$ cat input
a 1
b 3
a 4
b 2
c 1
$ cat input | awk '{x[$1]+=$2}END{for (k in x){print k,x[k]}}'
a 5
b 5
c 1
$ cat input | sort | sm2 1 1 2 2
a 5
b 5
c 1

チーム分け

結構適当でした。

前半戦

1問目

echo -12,135,123 135,123 を足すという問題。

  • 最初に試した方法 echo -12,135,123 135,123 | tr -d ',' | tr ' ' '+' | bc
  • 話の流れでいくと echo -12,135,123 135,123 | sed 's/,//g' | awk '{print $1+$2}'
  • 出力にも , を入れるなら "%'d" を使って echo -12,135,123 135,123 | sed 's/,//g' | awk '{printf "%'"'"'d\n", $1+$2}'
  • , を消すのも awk を使うなら echo -12,135,123 135,123 | awk '{gsub(",","");printf "%'"'"'d\n", $1+$2}'

2問目

以下のデータの順番を「名前 点数」に統一するという問題。

$ cat score
45 鎌田
浜田 72
今泉 84
54 上田
62 斉藤

勘違いして点数の方を左にしてしまっていました。

  • cat score | awk '$1~/[0-9]/{print $2,$1} $2~/[0-9]/{print $1,$2}'
  • cat score | sed 's/\([^0-9 ]*\) \([0-9]*\)/\2 \1/'

twitter 上で出ていた解答に比べて全然ダメな感じでした。

3問目

m/s に変換する問題。 (1マイル=1609m)

$ cat speed
100km/h
16mph
  • cat speed | sed 's,km/h,*1000/3600,;s,mph,*1609/3600,' | bc | sed 's,$,m/s,'
  • cat speed | awk '/km/{print $1*1000/3600,"m/s"}/mph/{print $1*1609/3600,"m/s"}'

bc だと整数になってしまうので、小数点以下の数値もほしければ awk を使って計算する方が良かったようです。 Google 電卓で検算できるのが便利でした。

4問目

さいとうさん、さわださん、ひろたさん、いとうさんの数を数えてください。

$ cat name
齋藤 斉藤 沢田 澤田 伊藤
齋藤 齊藤 広田 廣田

最初は総数を数えればいいのかと思って egrep -o 'さいとう|さわだ|ひろた|いとう' | wc -l というのを考えていたのですが、名前ごとにカウントだったようです。

全くと言って良い程リダイレクトが無くて cat ばっかりというツイートを受けて、頭に < input を付ける方法に切り替えました。 cat を使っているのは入力を先頭に書きたいからのはず、ということも合わせて、 あまり使われているのを見かけませんが、頭に付けるようにしました。

kakasi をインストールするのは面倒そうだったので、 sed でがんばって < name sed 's/[齋斉齊]藤/さいとう/g;s/[沢澤]田/さわだ/g;s/[広廣]田/ひろた/g;s/伊藤/いとう/g' | egrep -o 'さいとう|さわだ|ひろた|いとう' | sort | uniq -c | sort -n としました。

休憩時間中に Unicode の異体字データベースとかで同一視するためのデータがないか探してみたのですが、時間不足で見つけられませんでした。

後半戦

csv

csv の内容を全部足す問題。

$ cat csv
1,2,"123,456",-5,"-123,444"
6,7,8,"12",9

行ごととかいうこともなく、全部足せば良いという問題でした。

"" の処理はシェルに任せれば良いかと思ったら、 そういうことをするコマンドは xargs だったので、 <csv tr ',' ' ' | xargs -n1 | tr -d ' ' | awk '{a+=$1}END{print a}' という解答になりました。

この解答高い評価をもらいました。

matrix

行列の転置。

$ cat matrix
a b c
d e f
g h i

ファイルを複数回読むのはズルかと思って、 連想配列にためるようにして < matrix awk '{for(i=1;i<=NF;i++){m[i]=m[i]" "$i}}END{for(k in m){print m[k]}}'|sed 's/^ //' となりました。 端の処理がうまく出来なかったので、sed で後処理しました。 時間優先の時は、単独でうまく書けなくても、他の方法を組み合わせてなんとかする、と方法もありだと思います。

解答例では cat matrix | awk '{for(i=1;i<=NF;i++){print NR,i,$i}}' | sort -k2,2 | awk '{print $3} | xargs -n 3 ということで一度 行番号 桁番号 内容 という形式に変換するのがポイントと言っていました。

IPv6 その1

IPv6 アドレスの省略された 0 を復元する問題。

とりあえず時間内に解けることを優先して、 ` echo 2001:db8:20:3:1000:100:20:3 | xargs -d: -n1 | sed ‘s/^/0000/;s/.*(….)$/\1/’ | xargs | tr ‘ ‘ : | sed -e ‘s/:0000$//’` となりました。

後で確認したら xargs -d: は Mac OS X の xargs だと使えませんでした。 さらに xargs -d: -n1 だと余計な改行が付くので、最後に sed で削除しているのですが、 tr : ' ' | xargs -n1 にすればそもそも余計な改行が付かなかったようです。

それから、他の解答例をみると、前につめるのは 0000 じゃなくて 000 で十分でした。

IPv6 その2

時間内に解くために思いついた方法をどんどん試して <ipv6 awk -F: '{n=8-NF;for(i=0;i<=n;i++){sub("::",":0::")}sub("::",":")}1' | sed 's/:/:000/g;s/:[^:]*\([^:][^:][^:][^:]\)/:\1/g' となりました。

内容を書き換えると NF が壊れてしまうので i<=8-NF だとうまくいきませんでした。 そこで一度 n に保存してから for ループで 0 をつめていきました。 つめる場所を保存するために :: は残しておいて、後で : に置き換えています。

1Anarchy Golf 関連で知っていた ‘{print}’ の短縮のようなものです。

その1の時の方法は途中で複数行に分割していて、複数行の IPv6 アドレスを同時に扱うのには使えなかったので、 sed 's/:/:000/g;s/:[^:]*\([^:][^:][^:][^:]\)/:\1/g' にかわりました。 後ろ4文字を残すというのは、素直に [^:] を列挙しても twitter に投稿できる文字数に収まりました。

今回の問題だけなら良いのですが、先頭が 2001 で既に4文字になっているのを利用して、処理を省略しているので、汎用的にするなら、そこも処理する必要があります。

ruby なら ruby -r ipaddr -nle 'puts IPAddr.new($_).to_string' ipv6 で出来ました。

感想

twitter のハッシュタグ (#シェル芸) での他の人の解答を参考にしたり、自分の解答を紹介したりできたり、頭リダイレクトが流行ったりして面白かったです。

Disqus Comments

Kazuhiro NISHIYAMA

Ruby のコミッターとかやってます。 フルスタックエンジニア(って何?)かもしれません。 About znzに主なアカウントをまとめました。

znz znz


Published