日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です

「第61回シェル芸勉強会」リモート参加レポート

2022-09-24(土)「第61回シェル芸勉強会」が開催されたので、久しぶりに参加レポートを書いた。まずは上田会長、問題作成と勉強会ワンオペに感謝。参加者の皆様もお疲れ。今回はコロナ禍の影響でリモート参加。東京や福岡、大阪の会場は無しだった。次回はオフラインで会場に集まることが出来るのだろうか?

勉強会募集サイト

勉強会開催アナウンス

usptomo.doorkeeper.jp

勉強会開催報告など

シェル芸勉強会リンク集

b.ueda.tech

Youtubeライブ配信の録画

www.youtube.com

Togetterまとめ

togetter.com

勉強会の内容について

毎度のことながら、問題作成と解説の配信ワンオペの上田会長に感謝。 今回の問題は、データ処理の問題。題意の理解が難しいものもあった。

問題と解答

問題に使うデータファイルは、下記のGitリポジトリから取得。

$ git clone https://github.com/ryuichiueda/ShellGeiData.git
$ cd ShellGeiData/vol.61

問題や解答例は、TogetterまとめやYoutubeライブ配信の録画を参照いただきたい。以下、私なりの解答掲載させていただく。

Q1

要するに、入や出が2回以上連続で出現する行が矛盾しているということ。

sortコマンドで並べ替えのキーを1列目で指定し、重複する行をuniqで探せば良い?と考える人は多そうだ。しかし、sortコマンドは指定されていない列でも並べ替えをする。

# B君の出入り記録に注目
$ grep B inout
B 入  
B 出
B 入
B 出

# 2列目でも並べ替えが発生してしまう
$ sort -k1,1 inout | grep B
B 出
B 出
B 入
B 入

sortコマンドの-sオプションを使うと、指定した列以外では並びをそのままにする。(安定ソート)あとはuniq -dで重複した行を探して完成。

$ sort -s -k1,1 inout 
A 入  
A 出
B 入  
B 出
B 入  
B 出
C 入
C 入
C 出

$ sort -s -k1,1 inout | uniq -d
C 入

Q2

これは題意を勘違いして、3行以上連続と思い込んでいた。以下題意を間違えた解答だが、一応載せておく。 データを@で連結して一行にし、(@以外+@)が3回以上出現する箇所を正規表現で抜き出す。

# データを@で連結して一行にする
$ cat data | sed '$d' | tr '\n' @
abc de@fg@hi@@あいうえお かきくけこ さしす@せ@そ@@UN@KO@@@上田@山田@上田@山田@@不労@所得@@@わうぃううぇを@うぇうぇうぇうぇw@

# grepの-oオプションを使って、(@以外+@)が3回以上出現する箇所を正規表現で抜き出す。
$ cat data | sed '$d' | tr '\n' @ | grep -oE '([^@]+@){3,}' 
abc de@fg@hi@
あいうえお かきくけこ さしす@せ@そ@
上田@山田@上田@山田@

# @を改行に戻して、空行を消す(awk NF)。
$ cat data | tr '\n' @ | grep -oE '([^@]+@){3,}' | tr '@' '\n' | awk NF
abc de
fg
hi
あいうえお かきくけこ さしす
せ
そ
上田
山田
上田
山田
わうぃううぇを
うぇうぇうぇうぇw
0000

Q3

これはギブアップだった。ちょっと難しく考えすぎたかも。下記のように桁数が同じ数字の並びで別の行に分割すればよかった。解答例は、Youtube動画アーカイブやTogetterまとめを参照。

123456789 #1桁の数字
101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 #2桁の数字
100 #3桁の数字

Q4

いろいろな解答があると思うが、最初に思いついた解答がこちら。改行を@に変換して一行にし、末尾の@を削除する。

$ cat file1 | tr '\n' @ | sed 's/@$//' | tr @ '\n'
abc
def
ほげ

結果が分かりにくいのでodコマンドを使って検証。

$ cat file1 | od -t x1c
0000000  61  62  63  0a  64  65  66  0a  e3  81  bb  e3  81  92  0a
          a   b   c  \n   d   e   f  \n 343 201 273 343 201 222  \n
0000017

$ cat file1 | tr '\n' @ | sed 's/@$//' | tr @ '\n' | od -t x1c
0000000  61  62  63  0a  64  65  66  0a  e3  81  bb  e3  81  92
          a   b   c  \n   d   e   f  \n 343 201 273 343 201 222 【←末尾の改行'\n'が消えている】
0000016

他にはhead -c -1 file1など思いついた。headコマンドに-cオプションを指定すると、ファイルの先頭から引数で指定したバイト数表示する。引数の前に-があると、指定した引数を末尾から削除して表示する。

余談だが、この問題を見た時に思ったこと。 * Vimは行末に改行を必ず入れて保存される。(末尾に改行が無いファイルの場合でも) * 最近大量のテキストデータを扱うことが多いのだが、たまに末尾の行に改行が入っていないデータがあり、catコマンドでファイルを連結する時に問題が発生して困る。

Q5

最初に考えた解答がこちら。最初にデータを縦に並べ替え、行毎に奇数なら数字 @偶数なら@ 数字と表示する。

$ cat nums2 | grep -o . | awk '!($1%2){$2=$1;$1="@"}($1%2){$2="@"}{print}'
1 @
@ 2
@ 4
@ 2
3 @
@ 2
@ 8
5 @
@ 2
3 @
@ 0
9 @
7 @
5 @
9 @
@ 4
3 @

次にrsコマンドの-Tオプションで、行列を並べ替える。rsコマンドはUbuntuならsudo apt install rsでインストール。

$ cat nums2 | grep -o . | awk '!($1%2){$2=$1;$1="@"}($1%2){$2="@"}{print}' | rs -T
1  @  @  @  3  @  @  5  @  3  @  9  7  5  9  @  3
@  2  4  2  @  2  8  @  2  @  0  @  @  @  @  4  @

後は無用な空白を削除し@を空白に戻して完成。

$ cat nums2 | grep -o . | awk '!($1%2){$2=$1;$1="@"}($1%2){$2="@"}{print}' | rs -T | tr -d ' ' | tr '@' ' '
1   3  5 3 9759 3
 242 28 2 0    4 

上記の解答例とほぼ同じ処理内容で、awkと数字を使わない(ファイル名は使っている)解答がこちら。

cat nums2 | grep -o .| while read d; do [ $(($d%($$/$$+$$/$$))) -eq 0 ] && echo "@ "$d || echo $d" @"; done | rs -T | tr -d ' ' | tr @ ' '
1   3  5 3 9759 3
 242 28 2 0    4 

上記のポイントは下記の通り。

bashは$((算術式))の書式で、算術式展開が利用可能。例えば3に対する2の剰余を計算する例。

$ echo $((3%2))
1

ここで数字の2を得るために、シェルの特殊変数$$を利用する。(現在のシェルのプロセスIDを参照)

$ echo $(($$/$$))
1

$ echo $(($$/$$+$$/$$))
2

Q6

最初に考えた解答がこちら。まずawkをつかって、データを1つズラしながら4つずつ折り返す。

$ cat nums3 | awk '{for(i=1;i<=NF-3;i++){print $i,$(i+1),$(i+2),$(i+3)}}'
759 355 639 954
355 639 954 640
639 954 640 980
954 640 980 946
...(略)

更に1列目の3倍が4列目と同じ行のみ、1列目を表示して完成。

$ cat nums3 | awk '{for(i=1;i<=NF-3;i++){print $i,$(i+1),$(i+2),$(i+3)}}' | awk '$1*3==$4{print $1}'
237
284
9

次にawkを使わない方法。と言ってもbashのwhileループを使っているが。考え方は上記と同じ。

まずconvコマンドを使って、データを1つズラしながら4つずつ折り返す。

$ cat nums3 | conv 4
759 355 639 954
355 639 954 640
639 954 640 980
954 640 980 946
...

convコマンドは、@grethlenさん作のegzactに含まれるコマンド。

次にbashの算術式展開を使って1列目の3倍が4列目と同じか比較し、同じ場合のみ1列目を表示して完成。

$ cat nums3 | conv 4 | while read a b c d; do [ $(($a*3)) -eq $d ] && echo $a; done
237
284
9

Q7

小問1

題意の理解に時間がかかってしまい、勉強会の時間内ではギブアップ。後ほどトライした。

表の場合は1列目と末尾列の積、裏の場合は(1-1列目の値)と末尾列の積を計算し、列を追加して末尾列に書き足す。しかし全ての合計が1になるように正規化する必要がある。要するに追加する末尾列の合計を計算し、その合計で末尾列を割り算して再計算する。データを2回舐めることになる。

まず追加する末尾列の合計を計算する。

# 表の場合
$ awk '{sum+=$1*$NF}END{print sum}' prior 
0.5

# 裏の場合
$ awk '{sum+=(1-$1)*$NF}END{print sum}' prior
0.5

次に再度末尾列を計算するのだが、前回計算した合計で割り算をする。末尾列の合計値はxargsコマンドの-Iオプションを使って@の位置に指定する。

# 表の場合
$ awk '{sum+=$1*$NF}END{print sum}' prior | xargs -I@ awk '{print $0,$1*$NF/@}' prior
0 0.00990099 0
0.01 0.00990099 0.00019802
0.02 0.00990099 0.00039604

# 裏の場合
$ awk '{sum+=$1*$NF}END{print sum}' prior | xargs -I@ awk '{print $0,$1*$NF/@}' prior
0 0.00990099 0.019802
0.01 0.00990099 0.019604
0.02 0.00990099 0.0194059

最終的に考えた解答例がこちら。

# 表の場合
$ echo 表 | while read c; do awk '{sum+=$1*$NF}END{print sum}' prior | xargs -I@ awk '{print $0,$1*$NF/@}' prior; done
0 0.00990099 0
0.01 0.00990099 0.00019802
0.02 0.00990099 0.00039604
0.03 0.00990099 0.000594059
0.04 0.00990099 0.000792079
0.05 0.00990099 0.000990099
0.06 0.00990099 0.00118812
0.07 0.00990099 0.00138614
0.08 0.00990099 0.00158416
0.09 0.00990099 0.00178218
...(略)

# 裏の場合
$ echo 裏 | while read c; do awk '{sum+=(1-$1)*$NF}END{print sum}' prior | xargs -I@ awk '{print $,(1-$1)*$NF/@}' prior; done
0 0.00990099 0.019802
0.01 0.00990099 0.019604
0.02 0.00990099 0.0194059
0.03 0.00990099 0.0192079
0.04 0.00990099 0.0190099
0.05 0.00990099 0.0188119
0.06 0.00990099 0.0186139
0.07 0.00990099 0.0184158
0.08 0.00990099 0.0182178
0.09 0.00990099 0.0180198

小問2

考えた解答例がこちら。表か裏かで計算内容を変更。出来たpriorファイルは、表裏のデータが1つずつ変わる毎に上書き。

$ cat coin | grep -o . | while read c; do awk -v c=$c 'c=="表"{sum+=$1*$NF}c=="裏"{sum+=(1-$1)*$NF}END{print sum}' *a/*61/prior | xargs -I@ awk -v c=$c 'c=="表"{print $0,$1*$NF/@}c=="裏"{print $0,(1-$1)*$NF/@}' *a/*61/prior > temp ; mv temp ShellGeiData/vol.61/prior; done ; tail *a/*61/prior
0.91 0.00990099 0.0180198 0.00491449 0.00894437 0.0135665
0.92 0.00990099 0.0182178 0.00441644 0.00812625 0.0124611
0.93 0.00990099 0.0184158 0.00390638 0.00726587 0.0112628
0.94 0.00990099 0.0186139 0.00338435 0.00636258 0.00996871
0.95 0.00990099 0.0188119 0.00285029 0.00541555 0.00857519
0.96 0.00990099 0.0190099 0.00230423 0.00442412 0.00707906
0.97 0.00990099 0.0192079 0.00174617 0.00338757 0.00547694
0.98 0.00990099 0.0194059 0.00117612 0.0023052 0.00376541
0.99 0.00990099 0.019604 0.000594061 0.00117624 0.00194093
1 0.00990099 0.019802 0 0 0

終わりに

久しぶりにシェル芸勉強会の参加記事を書いたが、しばらく書いてなかったためか「書き慣れ」のレベルが落ちて、書くのに時間がかかる感じた、、 LTや二次会も面白かった。Lightning Talkって5分か10分程度だと思うのだが、もはや単なるTalkでは?と思う程に皆さん力の入れ方がすごい。 次回はオフラインで開催出来るのだろうか?コロナ禍の影響が心配だ。