[USP友の会] 第2回シェル芸人養成勉強会 行ってきました

どうもお久しぶりです。デスマ帰りの (っ´∀`)っ ゃー です。元気に生きてます。
デスマの最中でも #hbstudy や #techlion は行ってたのですが、ブログに書くだけの体力は残っていませんでした(;´Д`)
12月に入り、デスマも無事終了したので、ようやっとブログ書くことができましたw

さてさて、昨日はUSP友の会怪鳥ぷれぜんつ シェル芸人養成勉強会に行ってきました。
当日のまとめはこちら、当日のスライドはこちらになります。ぼくのブログでは、全10問のお題のうち、どう解答したかをいくつかピックアップして書くことにします。(全部書くとさすがに半日くらいかかるw)

なお、正解は1つではなく、正答例と同じ解答が得られれば、それだけ多くの解答が正解となります。ご参考までに、私の環境はCentOS 6.3で文字コードはja_JP.utf8です。

※ シェル芸とは

UNIXシェル(主にbash)のワンライナーを駆使して文字列加工を自由自在に操ることができる人をシェル芸人と呼びます。(by USP友の会会長 上田さん)

最初に感想を書きますと、tukubaiコマンドがあると確かに楽です。が、sedやawk、trを駆使して解答しても頭の体操になります。また、仕事で使うぶんには後者のほうが導入障壁は少ないはず。私のようにプログラミングスキルがない人間にとっては、新しいコマンドやツールは決して銀の弾丸ではなく、使いどころを理解できるまではおいそれと使えないかなぁ・・・と。一方、コマンド名がローマ字だったり(例えば「mojihame」とか)、何段もパイプを繋がずにコマンドライン一発で文字列整形や数値の集計ができたりするので、コマンドラインに不慣れな方にコマンドを叩いてもらうには、これもありかなとも思います。

参加者の皆さんのうち、今回は半数以上の方が猛者のようで、perlのワンライナーで解答されたり、一方でcommコマンドという知らないコマンドが出てきたりなど、新たな発見もあったりして、勉強になりました。

それではお題と私の解答を見ていきましょう。

#1 文字化けしたファイルの削除

文字化けしたファイルを生成するところから始めました。nkfコマンドが入っていなかったので、まずはyumでnkfをインストール。こちらはbaseリポジトリにあるので何も考えず「yum install nkf」でOKです。nkfがインストールされたら、以下のコマンドでワザと文字化けしたファイルを作成しました。

echo ほげ | nkf -s | xargs touch

nkfのオプション「-s」を与えると、入力した文字コードをShift-JISに変換します。これをxargsコマンド越しにtouchコマンドへ渡してあげて、文字化けした空ファイルを作ることができます。

さてさてファイルを消してみましょう。今回のお題は、カレントディレクトリに通常のASCII文字で名づけられたファイル「abc」「DEFG」もあります。これは消しちゃいけません。なので、文字化けしたファイル「のみ」を消すのが正解です。

ls [!a-zA-Z]* | xargs rm

これで、カレントディレクトリにある文字化けしたファイルのみが消えてくれました。sambaでwindowsファイル共有をしているサーバなどで役立ちそうですね。

#2 フィールド数がバラバラなファイルにある数字を足し算してみる

次のようなファイルがあったとして、これらの数字を全部足し算します。

$ cat num
1
2 3 4
5 6
7 8 9 10

恐らく参加者全員が「まずはこれらの数字を一列に並べる」から始めたでしょう。数字を一列に並べるため、私は改行をスペースに変換しました。

cat num | tr 'n' ' '

そして、exprコマンド(整数値の計算ができるコマンド)に食わせるため、スペースを「 + 」に変換してあげました。

cat num | tr 'n' ' ' | sed -e "s/ / + /g"

しかしこれでは、10の後にも加算記号がついてしまいます。なので、末尾のスペースを削除することにしました。

cat num | tr 'n' ' ' | sed -e "s/ $//g;s/ / + /g"

これで出力が「1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10」となったはずです。あとは以下のようにしてexprコマンドに食わせてあげます。

expr $(cat num | tr 'n' ' ' | sed -e "s/ $//g;s/ / + /g")

そうすると、答え「55」を求めることができました。awkを使った解答例やtukubaiコマンドを使った解答例は、上田さんのスライドを参考にしてみてください。

#3 以下のhogeファイルから、aとbについてそれぞれ一番大きな数字を求める

$ cat hoge
a 12
a 13
b 13
a 432
b 111
b 43

これは当日正解を出すことができませんでした。今もう一度復習がてら解いた答えがこれです。

for i in a b; do grep ^$i hoge | sort -k2 -n | tail -n 1; done

まず、ファイルhogeの先頭がaかbに該当するものをgrepし、sortコマンドを使って、2列目を数字順に並べ替え、それぞれ最後の1行を抜き出しています。ファイルの先頭文字列の指定はfor文のループで回しています。ここでファイルhogeの「b 43」を「b 4343」にしてみます。そして再度上記コマンドを実行すると、ちゃんとbの最大は4343であることが求められます。さらに、「b 111」を「b 111111」にしてみると、こちらも「b 111111」が最大であることがわかります。

#5 日付と曜日

1990年から2012年までの1月1日が「YYYYNNDD」形式で書かれたファイルがあったとして、何曜日が何日あるかを集計します。GNU dateでは「-f」引数が使えるので、以下のようにして直接dateコマンドに食わせて集計しました。

LANG=C date -f osyouga2 | awk '{print $1}' | sort | uniq -c

日本語だと曜日が4フィールド目になるのでこうですね。

date -f osyouga2 | awk '{print $4}' | sort | uniq -c

-fオプションが使えない場合どうしよう、と考えてみた結果、forループを使うことにしました。

for i in $(cat osyouga2); do date +%A --date=$i; done | sort | uniq -c

または

for i in $(cat osyouga2); do LANG=C date +%A --date=$i; done | sort | uniq -c

ただしこれだと曜日の並びがバラバラになってしまいます。なので、forループを入れ子にするしかなさそうです。。。

for A in 日曜日 月曜日 火曜日 水曜日 木曜日 金曜日 土曜日; do for i in $(cat osyouga2); do date +%A --date=$i; done | sort | uniq -c | grep $A; done

力技ですが、もっとエレガントな答えがあればいいなあw

#8 ファイルの比較

file2からfile1にない数字を抽出します。いずれも数字は縦に並んでいるものとします。

file1: 1 3 4 6 9
file2: 2 3 4 5 9

最初に思いついたのはdiffコマンドの結果を食わせる方法でした。

diff -y file1 file2 | egrep "|" | awk '{print $NF}'

しかし、「diff使っちゃだめ(笑)」って縛りが入ったのでこう解答しました。

cat file2 | grep -v -f file1

grepの「-v」オプションは、引数に含まれないものを標準出力に表示します。「-f」はパターンをファイルから1行ごとに読み込みます。ゆえに、これらオプションの組み合わせで同じ解答を求めることができました。

当日の会場にはさらに猛者がいて、commコマンドを使って解いていました。

comm -13 file1 file2

オプションが与えられないとcommコマンドは三列に出力を行うので、「じゃあ1列目と3列目を削っちゃえばよくね?」という至極単純な発想で、パイプを使わないぶん、巨大なファイル比較を行った場合、パイプでgrepやawkに渡すより速そうですね。

家での復習も兼ねて解いてみて気づいたのですが、正解が複数ある問題を解くというのは、本当に頭の体操になるなあ、と。そして、コマンドやオプションのボキャブラリーが多いと、イザというとき本当に役立ちます。シェル芸人としてまだまだ精進せねば。