「第45回シェル芸勉強会:福岡サテライト」レポート
AIP Cafeで始まったシェル芸勉強会の福岡サテライト会場ですが、今回は久しぶりにAIP Cafeでの開催でした。参加者は2人の方がキャンセルされて3人でした。1人は初参加で、1人は長崎サテライト会場に参加の経験がある方が福岡へやってこられました。2人とも会場の場所が分かりづらかったとのことで、AIP Cafeは場所が初見殺しだったことを忘れておりました。
勉強会の情報
勉強会主催者上田さんが公開されているリンク集をご覧ください。
スライド資料
最初に話す時間が取れないので、イントロと言うより何だろう??
午前の部
午前中はぷるさんの自宅から配信されたJavaScript入門を自宅で拝見いたしました。動画はこちらです。
https://www.pscp.tv/w/1zqKVEXyXAaxBwww.pscp.tv
JavaScript初心者が呟いた小並感溢れるツイート。 そもそも非同期処理とかやる機会が最近は全然無い。 そもそも最近お仕事でコードを書いていない。
twitter.com非同期処理はJSっぽい環境で開発してた時に頭が混乱した記憶が #シェル芸
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.comああ条件分岐とかをメソッドチェーンで繋ぐことが出来るのか。 #シェル芸 #javascript
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.com世の中には #JavaScript っぽいけどモダンな仕組みとか全然利用出来ないような特殊環境がありましてですね・・非同期処理を含め苦労した記憶が・・ #シェル芸
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.compromiseは並列処理のデザパタなのね #シェル芸 #JavaScript
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.comどれが非同期処理の関数なのかわからなくなる #ヤバそう #シェル芸 #JavaScript
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.com最近はTypeScriptとか使って直接JavaScript書くことは減ってるのかな? #シェル芸 #JavaScript
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.com#JavaScript 初心者なので、指定した秒数待つ処理を書いたら画面全体が固まって目が点になった記憶があるぞ! #シェル芸
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
twitter.comバックグラウンド処理は非同期と言えるか? #シェル芸
— ぱぴろんちゃん😱🙀 (@papiron) 2019年12月28日
配信を見た後、会場のAIP Cafeに向かって移動を開始。
午後の部
午後からはいつものように、上田さんが出題された問題に取り組む時間です。自分なりの解答例を記載いたします。今回は難易度がやや下がった感じがありました。個人的には出題者上田さんによるQ6解答例が印象に残りました。
Q1
データを並べ替え、指定したキーの中で最後の行のみを抽出する問題。
日付が固定長じゃないという意地悪付きですが、GNU sortコマンドの-V
というバージョン名で並べ替えるというオプションを利用しました。ebanさんに教えていただきましたが、-V
オプションを使うと数字以外は全部区切りになるようです。
twitter.comsort -V: 数字以外は全部区切りになるようだ
— eban (@eban) 2019年12月28日
"gnulib/filevercmp.c at master · coreutils/gnulib"
https://t.co/dLUa5us1xX
まずは最後の行のみを抽出する関係で-r
オプションもつけて逆順に並べ替えます。
$ nkf data.csv | sort -r -V 2019/12/30,トマト,4個 2019/12/21,バナナ,5個 2019/12/9,バナナ,4個 2019/12/6,トマト,3個 2019/12/1,トマト,7個 2019/11/23,バナナ,2個 2019/11/21,ピーマン,32個 2019/11/8,ピーマン,31個 2019/11/2,トマト,1個
次に2行目の野菜をキーにして、1行目の日付が最も新しい(つまり最初の行)のみを抽出します。 uniqコマンドを使う手がありますが、awkの連想配列を使った解答がこちらです。
$ nkf data.csv | sort -r -V | sort -s -t, -k2,2 | awk -F, '!a[$2]++' 2019/12/30,トマト,4個 2019/12/21,バナナ,5個 2019/11/21,ピーマン,32個
なぜ最初の行のみが抽出出来るのか、以下に解説してみます。 まず2列目をキーにした配列aの値を確認します。
$ nkf data.csv | sort -r -V | sort -s -t, -k2,2 | awk -F, '{print $2,a[$2]++}' トマト 0 トマト 1 トマト 2 トマト 3 バナナ 0 バナナ 1 バナナ 2 ピーマン 0 ピーマン 1
次に配列aの値に対して論理否定を指定します。 awkでは0は偽で0以外は真なので、値が0の時(つまり最初の行)のみ1(真)になります。 この真偽値をパターンで利用して、2列目をキーにして最初の行のみを抽出します。
$ nkf data.csv | sort -r -V | sort -s -t, -k2,2 | awk -F, '{print $2,!a[$2]++}' トマト 1 トマト 0 トマト 0 トマト 0 バナナ 1 バナナ 0 バナナ 0 ピーマン 1 ピーマン 0
キーにする列を使った連想配列を使ってuniqコマンドと同様の動きを実現するテクニックは、日本GNU AWKユーザー会の会長である斉藤博文さんの著書で知りました。 www.shoeisha.co.jp
ユニケージ開発で利用されるTukubaiコマンドで、キーの列を指定して最初もしくは最後を抽出するコマンド(getfirst、getlast)があるのでこれを利用する手もあります。
$ nkf data.csv | sort -V | sort -s -t, -k2,2 | tr ',' ' ' | getlast 2 2 2019/12/30 トマト 4個 2019/12/21 バナナ 5個 2019/11/21 ピーマン 32個
Q2
問題の意図を完全に見誤っていました、論外でしょ私、、、 データの2列目に記載された日毎の株価終値について、月毎に最大値と最小値を出そうという問題。
最大値と最小値を同時に処理するため、moreutils付属のpee
を使った解答例が出ていたので、福岡サテライトではpeeコマンドについて解説をしました。
pee
はパイプに流れてきたデータを、2つのコマンドでそれぞれ処理させたい時に使います。
$ seq 1 3 | pee 'cat' 'tac' 1 2 3 3 2 1
$ seq 1 3 > temp $ sed 's/$/どすえ/' temp > temp #【入出力で同じファイル指定】 $ cat temp #【ファイルの中身が消えちゃった!】 $ seq 1 3 > temp #【再度挑戦】 $ sed 's/$/どすえ/' temp | sponge temp #【sponge使ってみる】 $ cat temp #【中身は消えない】 1どすえ 2どすえ 3どすえ
私はawkで頑張る解答を考えていたが、時間内で完成出来ませんでした。 勉強会終了後下記の解答例を考えましたが、最大値や最小値を地道に選び出す考え方です。 2次元配列を使う意味はあまり無いですね、使ってみたかっただけです。 真の2次元配列はGNU awkの新しいバージョンのみサポートされています。
$ nkf nikkei_stock_average_daily_jp.csv | sed '1d;$d' | tr -d '"' | tr , ' ' | awk '{print substr($1,1,7),$2}' | awk 'pre_month!=$1{min=100000;max=0;}$2>max{amax[$1]=$2;max=$2}$2<min{amin[$1]=$2;min=$2}{pre_month=$1}END{for(i in amax)print i,amax[i],amin[i]}' | sort #【GNU awkで真の2次元配列を使った例】 $ nkf nikkei_stock_average_daily_jp.csv | sed '1d;$d' | tr -d '"' | tr , ' ' | awk '{print substr($1,1,7),$2}' | awk 'pre_month!=$1{min=100000;max=0;}$2>max{a[$1][1]=$2;max=$2}$2<min{a[$1][2]=$2;min=$2}{pre_month=$1}END{for(i in a){print i,a[i][1],a[i][2]}}' | sort #【awkで擬似的な2次元配列を使った例】 $ nkf nikkei_stock_average_daily_jp.csv | sed '1d;$d' | tr -d '"' | tr , ' ' | awk '{print substr($1,1,7),$2}' | awk 'pre_month!=$1{min=100000;max=0;}$2>max{a[$1,1]=$2;max=$2}$2<min{a[$1,2]=$2;min=$2}{pre_month=$1}END{for(i in a){split(i,b,SUBSEP);print b[1],b[2],a[b[1],b[2]]}}' | sort | awk 'p==$1{printf " "$3}p!=$1{printf "\n"$1" "$3}{p=$1}' | awk NF
Q3
何行目が違うかではなくて、最初の文字から通算して数える問題。
バイナリデータを比較して異なるバイトのファイル先頭からの位置を出力するcmp
コマンドで出来るか?と思いましたが、時間内に出来ませんでした。
後ほど書きの解答を考えてみました。各文字は8バイトであり、少数の切り上げ処理はRubyが楽だったので利用しました。
少数を切り上げているのは、8バイト中いずれかのバイトに差異があった場合でも対応するためです。
$ cmp -l <(tr -d '\n' < flags_a) <(tr -d '\n' < flags_b) | ruby -alne 'puts ($F[0].to_f/8).ceil' | uniq 39 65
— シェル芸bot(停止しました) (@minyoruminyon) 2019年12月31日twitter.com
Q4
結合文字が混じっているのが意地悪な要因。
単純にgrep -o .
とかだと結合文字がバラされて失敗します、さて困りました。
ちょっとズルをした解答例を考えました。
16進数で1バイト毎にダンプすると、文字はe8
またはe9
で始まっていることを利用しています。
e8
やe9
の直前に改行文字0a
を挿入しています。
$ cat nabe | od -t x1 -An | tr -d '\n' | sed -r 's/ e(8|9) / 0a &/g' | xxd -p -r | sed 1d 部 邊 (以下略)
Q5
地道に文字を全パターン切り出し、それぞれが回文になっているか確認しました。 まず確認する文字の全パターン切り出しをawkで頑張ります。
$ cat message | awk -F '' '{for(i=1;i<=NF-3;i++){for(j=1;j<=NF-i;j++)print substr($0,i,j)}}' き きつ きつつ きつつき (略) つ つつ つつき つつきと (略) す すが すがし すがしか が がし がしか
後はそれぞれの行が回文になっているかを確認します。確認方法は下記の通りです。
# 回文の場合は表示される $ echo きつつき | pee cat rev | uniq -d きつつき # 回文では無い場合は表示されない $ echo たけやぶ | pee cat rev | uniq -d
上記の確認方法を使った解答例がこちらです。 ループを使って上記コマンドを何度も呼び出しているので、実行速度がとても遅いです。
$ cat message | awk -F '' '{for(i=1;i<=NF-3;i++){for(j=1;j<=NF-i;j++)print substr($0,i,j)}}' | while read l; do echo $l | pee cat rev | uniq -d; done | sort | uniq | awk 'length>1' かしがすきすきすがしか がすきすきすが (略) ゆんゆ んゆん
Q6
最初問題の意図が理解出来ず。 参加者の方に意味を教えていただきました、ありがとうございます。
結局自力では解けず、出題者上田さんの解答解説で考え方を勉強させていただきました。 まずは各行をキーにしてファイル全体をgrep -oで検索して切り出します。 次に複数行表示される場合は部分的な回文なので、1行のみ表示される物だけを選べば良いということですね。
Q7
無限に出力する解答は出来ませんでした。
小数点以下10万桁まで限定のイカサマ的解答がこちらです。
scale=
で指定する桁数が大きすぎると、危険なので注意してください。
(bcコマンドが使うメモリ量が膨大になるため)
$ echo 1 7 | sed 's@\(.\) \(.\)@scale=100000;\1/\2@' | bc | tr -d '\\\n'
Q8
ギブアップでした。
終わりに
久しぶりにAIP Cafeで福岡サテライトを開催しました。 会場の広さ的にはこのくらいの方が参加者のサポートがやりやすいと思いました。 持ってきたスピーカーがショボすぎたので、次回AIP Cafeで開催する時はマトモなスピーカーを準備するか、AIP Cafe設置のスピーカー活用を考えてみます。
僕以外の参加者は2人でしたが、1人は長崎サテライトで参加していただいている学生さんでした。 実家に帰ってきてるので福岡サテライトへ参加いただいたようです。もう1人は初参加の方。Linuxサーバ管理とかをやっているが、コマンドの使い方を勉強したいというのがキッカケとのことでした。
自分なりにサポートや解説を頑張ってみたのですが、果たしてどうだったでしょか?かしら??問題の難易度的にはやや落ち着いた感じではありましたが、今後もサポートや解説は出来るだけやってみたいと思いました。