放課後プログラミング

調べたことや考えたことなどを忘れないために書きます。

シェル芸勉強会にUst参加しました

会社の同期に誘われてシェルの勉強会に遠隔参加してみました。

以下が勉強会で使われた資料です。

ツイッター実況
https://twitter.com/hashtag/%E3%82%B7%E3%82%A7%E3%83%AB%E8%8A%B8

前半戦

Q1

・次のechoの出力を、echoにパイプをつなげて足し算してください。

$ echo -12,135,123 135,123
-12,135,123 135,123

以下のように解きました

$ echo -12,135,123 135,123 | sed -e "s/[ ,]/\n/g" | awk '{sum+=$0} END{print sum}'
504

問題を勘違いしてて-12135123 + 135123という意味でした

ツイッターに流れた解答

sedではなくtrで十分でした
これも知らなかったです↓

$ echo a b | awk ''
$ echo a b | awk '1'
a b
$ echo a b | awk '2'
a b
$ echo a b | awk '$0'
a b
$ echo a b | awk '$1'
a b
$ echo a b | awk '$2'
a b
$ echo a b | awk '$3'
$ echo a b | awk '{}'
$ echo a b | awk '{$0}'
$ echo a b | awk '{}1'
a b
$ echo a b | awk '{print $0}1'
a b
a b

perlで足し算してる方もいました、単純な計算をする方法はたくさんありそう

Q2

・次のメモについて、各レコードが「名前 点数」の順になるようにデータを整形しましょう。

$ cat score
45 鎌田
濱田 72
今泉 84
24 上田
94 斉藤

以下のように解きました

$ cat score | awk '/^[0-9]/{print $2,$1 ; next} {print $0}'
鎌田 45
濱田 72
今泉 84
上田 24
斉藤 94

ツイッターに流れた解答

代入使って出力場所一箇所にできるのは思いつかなかったです

awkでしていた置換sedで十分でした

Q3.

・m/sに直してください
 ・1マイル = 1.609km で演算を

$ cat speed
100km/h
16mph

以下のように解きました

$ cat speed | sed -e "s/^\([0-9]*\)/\1 /g" | awk '{ if($2=="km/h"){res = $1*1000/3600;printf "%sm/s\n",res} if($2=="mph"){res = $1*1.609*1000/3600;printf "%sm/s\n",res}}'
27.7778m/s
7.15111m/s

ツイッターに流れた解答

数値を抽出して計算するときとても便利そうなTips

$ cat speed | awk '{print $1}'
100km/h
16mph
$ cat speed | awk '{print $1*1}'
100
16
$ echo 1a2 | awk '{print $1*1}'
1

2番目の数値は見えないみたいです

sedとbcを組み合わせるとゴミの混ざった文字列からいろいろ計算できそうです
bcコマンドに渡す計算式の先頭にscale=4;を付けておくと小数点以下4桁までで丸めてくれるらしい

$ cat speed | sed 's/km/*1000/;s@mp@*1609/@;s/h/3600/;' | bc
27
7
$ cat speed | sed 's/km/*1000/;s@mp@*1609/@;s/h/3600/;' | sed 's/^/scale=4;/' | bc
27.7777
7.1511

Q4.

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

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

以下のように解答しました

$ cat name | sed "s/ /\n/g" | awk '/[斉斎齊齋]藤/{saito++} /[沢澤]田/{sawada++} /[広廣]田/{hirota++} /伊藤/{itou++ } END{printf "saito\t%s\nsawada\t%s\nhirota\t%s\nitou\t%s",saito,sawada,hirota,itou}'
saito   4
sawada  2
hirota  2
itou    1

ツイッターに流れた解答

表記の振れをsedで解消してからシンプルに数えても同じようにできました

kakasiという形態素解析エンジンを使う解ありました
http://ja.wikipedia.org/wiki/KAKASI
表記の振れのパターンが多い時はこうすれば簡単そうです

後半戦

Q5.

・次のCSVに書いてある数字を足し算してください。

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

以下のように解きました

$ cat csv | sed -e "s/\"\(-*[0-9]*\),*\([0-9]*\)\"/\1\2/g" | sed "s/,/\n/g" | awk '{sum+=$0}END{print sum}'
52

ツイッターに流れた解答

xargsというコマンドは主にfindコマンドの結果を別のコマンドの引数に並べるために使われる

$ touch a.txt b.txt c.txt
$ find . -name "*.txt" -type f
./a.txt
./b.txt
./c.txt
$ find . -name "*.txt" -type f | xargs
./a.txt ./b.txt ./c.txt
$ find . -name "*.txt" -type f | xargs rm
$ find . -name "*.txt" -type f
$

-n1オプションで逆の処理が行える

$ touch a.txt b.txt c.txt
$ find . -name "*.txt" -type f
./a.txt
./b.txt
./c.txt
$ find . -name "*.txt" -type f | xargs
./a.txt ./b.txt ./c.txt
$ find . -name "*.txt" -type f | xargs | xargs -n1
./a.txt
./b.txt
./c.txt

このとき、"'で囲ってると1つの要素としてカウントしてくれることを利用しているようです。

$ echo \"a b\" c
"a b" c
$ echo \"a b\" c | xargs -n1
a b
c

Q6.

・次のデータを行列として転置してください。

$ cat matrix
a b c
d e f
g h i
###出力###
$ ???
a d g
b e h
c f i

解き方わからず。

ツイッターに流れた解答

配列の中身の遷移は

a[1] <= " a"
a[2] <= " b"
a[3] <= " c"
a[1] <= " a d"
a[2] <= " b e"
a[3] <= " c f"
a[1] <= " a d g"
a[2] <= " b e h"
a[3] <= " c f i"
a[1] <= "a d g"
a[2] <= "b e h"
a[3] <= "c f i"

$1=$1をすると$0の各要素の間にある余計なスペースは削されるみたいです

$ echo " a  b " | awk '1' | od -a
0000000  sp   a  sp  sp   b  sp  nl
0000007
$ echo " a  b " | awk '$1=$1' | od -a
0000000   a  sp   b  nl
0000004

調べたら下記URLの項目27にこの使い方が載っていました。
http://www.catonmat.net/blog/awk-one-liners-explained-part-two/

To remove whitespace between fields you may use this one-liner:

awk '{ $1=$1; print }'

This is a pretty tricky one-liner. It seems to do nothing, right? Assign $1 to $1. But no, when you change a field, Awk rebuilds the $0 variable. It takes all the fields and concats them, separated by OFS (single space by default). All the whitespace between fields is gone.

Q7.

・次のIPv6IPアドレスから、省略された0を復元してください。
 ・4桁の頭のゼロは省略できる

$ echo 2001:db8:20:3:1000:20:3
2001:db8:20:3:1000:20:3

以下のように解きました

$ echo 2001:db8:20:3:1000:20:3 | sed "s/:/\n/g" | awk '{while(length($0)<4){$0 = "0" $0} if(NR!=1){printf ":"} printf "%s",$0}'
2001:0db8:0020:0003:1000:0020:0003

ツイッターに流れた解答

0を補充してから後方4文字を抜き出す方法で、length()を見ながら足していくより高速にできそうです
-I@@を元の文字として続く処理の出力で置換することができる。

$ echo 1:2 | xargs -d:
1 2

$ echo 1:2 | xargs -d: | od -a
0000000   1  sp   2  nl  nl
0000005
$ echo 1:2 | xargs -d: -I@ echo A@A
A1A
A2
A
$ echo 1:2 | xargs -d: -I@ echo A@A | od -a
0000000   A   1   A  nl   A   2  nl   A  nl
0000011
# -dオプションをつけると改行も普通の入力文字として処理するので
# 最初のechoの改行によって意図しない場所で改行される
$ echo -n 1:2 | xargs -d: -I@ echo A@A
A1A
A2A
$ echo -n 1:2 | xargs -d: -I@ echo A@A | od -a
0000000   A   1   A  nl   A   2   A  nl
0000010

sedも難しいので分解します

$ cat test
hogepiyo
fugamoge
foobar

# sedはデフォルトで入力された全てを処理が終わったら出力します
$ cat test | sed ""
hogepiyo
fugamoge
foobar

# pはマッチした行を出力します
$ cat test | sed "1p"
hogepiyo
hogepiyo
fugamoge
foobar

# -nでデフォルトの出力を消せます
$ cat test | sed -n "1p"
hogepiyo

# このようにしてhogeのある行にマッチさせられます
$ cat test | sed -n "/hoge/p"
hogepiyo

# /./は空白ではない行にマッチします
$ cat test | sed -n "/./p"
hogepiyo
fugamoge
foobar

# 置換して後方4文字だけを残します
$ cat test | sed "s/.*\(....\)$/\1/"
piyo
moge
obar

# 空白でない行に対して後方4文字を残し、それらの該当行のみ出力します
$ cat test | sed -n '/./s/.*\(....\)$/\1/p'
piyo
moge
obar

僕の環境(Cygwin, GNU sed 4.2.2)では各sed置換の間をスペースではなくセミコロンにしたら動きました
こちらの解法も0を補充してから後方4文字を抽出していますが、処理中で各フィールドを分割していない点が異なっていました

Q8.

・次のIPv6IPアドレスから、省略された0を復元してください。
 ・4桁の頭のゼロは省略できる
 ・1回だけ、:0000:0000:と0000が続くところは::と省略できる。

$ cat ipv6
2001:db8::1234:0:0:9abc
2001:db8:20:3:1000:100:20:3
2001:db8::9abc

解き方わからず。

ツイッターに流れた解答

awk -F:は区切り文字を:に指定します。
IPv6アドレスは省略しない場合8フィールドあるので、8-NF:::0::に置換してから:::に置換し、最後にQ7と同じ処理をして解かれています。

おわり

知らなかったコマンドが普通に使われていたりで僕にとっては高度な内容でしたが、慣れ始めていたawkをいろいろいじれたり他の方の解答も見れたりで多く学ぶことができました。
主催者の方と実況者の方ありがとうございました。

※もし都合の悪い引用があればご連絡ください