第29回シェル芸勉強会参加報告
2017-07-01(土)に開催された、第29回シェル芸勉強会(jus共催 第29回激しいシェル芸勉強会)に参加しました。
ただ、当日は体調不良で遠隔での参加もできなかったため、Youtubeで配信してくださったライブ中継のアーカイブを利用して、自主勉強をしました。
勉強会は、午前と午後の2部構成でした。
午前は、シェルに関する勉強会で、Perlについて学習する内容でした。
午後は、シェル芸勉強会で、シェル芸力を付けるための問題を解きました。
以下、勉強会の詳細を記します。
実行環境
Arch Linux 4.9.34-1-lts
GNU bash 4.4.12
GNU coreutils 8.27-1
GNU diffutils 3.6-1
grep (GNU grep) 3.1
sed (GNU sed) 4.4
gawk 4.1.4
w3m 0.5.3+git20170102
usp Personal Tukubai (20170606、 Open usp Tukubaiで代用可能)
シェルに関する勉強会
Perlワンライナー入門
USP友の会 鳥海 秀一( @hid_tori )さんによる、Perlワンライナーの書き方や代表的なオプションなど学ぶ内容でした。
資料はこちらから。
https://umidori.github.io/shellgei-29th-am/index.html
題目のとおり、Perlの基本から、Perlをワンライナーで動かす際に有用な情報まで、様々なテクニックが紹介されていました。
個人的には、Perlには、AWKの RS
のような、有用な組込変数が多く存在することが、非常に印象的でした。
ちなみに、本Perl講義は、次回も引き続き開催されるそうで、次回の内容は「Perl正規表現」だそうです。
Perlの正規表現は、前回の第28回シェル芸勉強会で、非常に有用だったので、とても気になる…!!
第29回シェル芸勉強会
問題文および模範解答は、以下のURLから。
https://blog.ueda.asia/?p=9870
問1
複数の得点リストを結合する問題。
第一問だけあって、まだ簡単な問題。awk
なり Tukubaiの sm*
なりで集計・ID順にソートするだけ。
#!/bin/bash
cat kadai{1,2} | # 元データの出力
### 1:ID 2:名前 3:得点
awk '{sum[$1" "$2]+=$3} END{for(n in sum){print n,sum[n]}}' | # 1:ID, 2:名前をキーとして集計
sort -k 1,1 # 出力のソート
Tukubai版。
#!/bin/bash
cat kadai{1,2} | # 元データの出力
### 1:ID 2:名前 3:得点
sort | # ソート
sm2 key=1/2 # 1,2フィールドをキーとして集計
以下、コピペ実行用のワンライナーコード。
cat kadai{1,2} | awk '{sum[$1" "$2]+=$3} END{for(n in sum){print n,sum[n]}}' | sort -k 1,1
Tukubai版。
cat kadai{1,2} | sort | sm2 key=1/2
問2
出欠簿の出欠フィールドに、最新の出欠状況を反映させる問題。
6回目の出欠状況が記録されているデータ attend6
を、「1:出席番号 2:第6回目の出席」の書式に変換し、 attend
と結合してやる。
両ファイルを結合した出力が得られたら、不足している「第6回目の欠席」データを補って、出力を整形すれば良い。
また、Tukubaiの loopj
を使う場合、結合する際に不足フィールドを補う機能があるため、処理内容が少しシンプルになる。
#!/bin/sh
cat attend6 | # 元データの出力
tr ',' '\n' | # 行列変換
### 1:ID
sed 's/$/ 出/' | # 出席データフィールドを追加
### 1:ID 2:第6回目の出欠状況
sort -k 1,1 | # 第一フィールドをキーとしてソート
join -1 1 -2 1 -a 1 attend - | # 入力1,2をそれぞれ第一フィールドをキーとして結合(結合できないレコードも表示)
### 1:ID 2:名前 3:出席状況 4:第6回目の出欠状況(欠席者は無し)
awk 'NF==4{print $1,$2,$3$4} NF==3{print $1,$2,$3"欠"}' # 欠席データを付与&出力を整形
### 1:ID 2:名前 3:出席状況
Tukubai版。
#!/bin/sh
cat attend6 | # 元データの出力
tr ',' '\n' | # 行列変換
### 1:ID
sed 's/$/ 出/' | # 出席データフィールドを追加
### 1:ID 2:第6回目の出席状況
sort -k 1,1 | # 第一フィールドをキーとしてソート
loopj -d'欠' num=1 attend - | # 入力1,2をそれぞれ第一フィールドをキーとして結合(不足フィールドを'欠'として補う)
### 1:ID 2:名前 3:出欠状況 4:第6回目の出欠状況
sed 's/ //3' # 余分な空白の除去
### 1:ID 2:名前 3:出欠状況
以下、コピペ実行用のワンライナーコード。
cat attend6 | tr ',' '\n' | sed 's/$/ 出/' | sort -k 1,1 | join -1 1 -2 1 -a 1 attend - | awk 'NF==4{print $1,$2,$3$4} NF==3{print $1,$2,$3"欠"}'
Tukubai版。
cat attend6 | tr ',' '\n' | sed 's/$/ 出/' | sort -k 1,1 | loopj -d'欠' num=1 attend - | sed 's/ //3'
問3
これまでの出欠状況に応じて、試験の得点を出力する問題。
スマートな方法を思いつかなかったので、出席回数・開講回数を出力して、愚直に出席回数が満たしているかどうかを判定する。
判定処理は一度にやるのではなく、出席・開講回数を一度フィールドとして追加し、後に awk
等で判別処理をする形式にすると、若干シンプルかも。
#!/bin/sh
cat attend | # 元データの出力
### 1:ID 2:名前 3:出欠状況
awk '{print $0, gsub(/出/, ".", $3), length($NF)}' | # 4:出席回数 5:開講回数フィールドを追加
### 1:ID 2:名前 3:出欠状況 4:出席回数 5:開講回数
join -11 -21 -a1 - test | # それぞれ第一フィールドをキーとして結合(結合できないレコードも表示)
### 1:ID 2:名前 3:出欠状況 4:出席回数 5:開講回数 6:得点(不参加者は無し)
awk '$4<=$5/2{$NF=0; print; next} NF!=6{print $0, 0} NF==6' | # 出席回数および得点の有無に応じて、6:得点フィールドの出力を変更
### 1:ID 2:名前 3:出欠状況 4:出席回数 5:開講回数 6:得点
awk '{print $1, $2, $NF}' # 不要なフィールドの除去
### 1:ID 2:名前 3:試験得点
Tukubai版。
#!/bin/sh
loopj -d'0' num=1 attend test | # 第一フィールドをキーとして結合(不足フィールドは0で埋める)
### 1:ID 2:名前 3:出欠状況 4:得点
awk 'atd=$3{print $0, length(atd), gsub(/欠/, "", atd)}' | # レコード末尾に5:開講回数 6:欠席回数 フィールドを追加
### 1:ID 2:名前 3:出欠状況 4:得点 5:開講回数 6:得点
awk '$6/$5 <= 0.5{print $1,$2,$4} 0.5 < $6/$5{print $1,$2,0}' # 6:欠席回数 / 5:開講回数 の割合に応じて、出力を整形
### 1:ID 2:名前 3:得点
以下、コピペ実行用のワンライナーコード。
cat attend | awk '{print $0, gsub(/出/, ".", $3), length($NF)}' | join -11 -21 -a1 - test | awk '$4<=$5/2{$NF=0; print; next} NF!=6{print $0, 0} NF==6' | awk '{print $1, $2, $NF}'
Tukubai版。
loopj -d'0' num=1 attend test | awk 'atd=$3{print $0, length(atd), gsub(/欠/, "", atd)}' | awk '$6/$5 <= 0.5{print $1,$2,$4} 0.5 < $6/$5{print $1,$2,0}'
問4
数列に対して、正負の有無・桁数毎にレコードに分けて出力する問題。
この問題は小問1,2と分かれているけれど、どちらも正答を出せる解答を考えてみた。
正負記号の有無、数値長毎に、 awk
の連想配列を使って集計・結合していく。
問3と同様に、一変に集計・結合処理をするのではなく、一度正負フラグや数値長を別フィールドとして出力してやると、
処理がわかりやすくなると思う。
#!/bin/sh
### Q4.1
echo -1 4 5 2 42 421 44 311 -9 -11 | # 数列の出力
tr ' ' '\n' | # 1レコード1フィールドに整形
sort -n | # 数値順でソート
awk '{print $0,sub(/-/, "", $0),gsub(/[0-9]/, "", $0)}' | # 1:数値, 2:正負フラグ 3:数値長 を出力
### 1:数値 2:正負フラグ 3:数値長
awk '{arr[$2,$3] = arr[$2,$3]" "$1} END{for(n in arr){print arr[n]}}' | # 2:正負フラグ,3:数値長 をキーとして、1:数値を集計
sort -k 1,1n # カテゴリ順にソート
### Q4.2
echo -1 +4 5 2 42 421 44 311 -9 -11 |
tr ' ' '\n' |
sort -n |
awk '{print $0,sub(/-/, "", $0),gsub(/[0-9]/, "", $0)}' |
awk '{arr[$2,$3] = arr[$2,$3]" "$1} END{for(n in arr){print arr[n]}}' |
sort -k 1,1n
以下、コピペ実行用のワンライナーコード。
echo -1 4 5 2 42 421 44 311 -9 -11 | tr ' ' '\n' | sort -n | awk '{print $0,sub(/-/, "", $0),gsub(/[0-9]/, "", $0)}' | awk '{arr[$2,$3] = arr[$2,$3]" "$1} END{for(n in arr){print arr[n]}}' | sort -k 1,1n
echo -1 +4 5 2 42 421 44 311 -9 -11 | tr ' ' '\n' | sort -n | awk '{print $0,sub(/-/, "", $0),gsub(/[0-9]/, "", $0)}' | awk '{arr[$2,$3] = arr[$2,$3]" "$1} END{for(n in arr){print arr[n]}}' | sort -k 1,1n
問5
テキストで表記された正三角形を、時計回りに回転させる問題。
Tukubaiの tateyoko
コマンドを用いたら、かなりシンプルに出来た!
解説は長くなるので後述。
まずはTukubai版から。
#!/bin/sh
cat triangle | # 元データの出力
tateyoko -i | # 転置処理(不足フィールドは補う)
tr -d '_' | # 余分な文字の除去
tac | # レコードの反転
tateyoko -i | # 転置処理(不足フィールドは補う)
tr -d '_' | # 余分な文字の除去
tac # レコードの反転
処理過程をコマンド毎に表すと、以下のようになる。
まず、元データの状態。
$ cat triangle
1
3 9
7 a 6
8 4 2 5
この元データに対して、 tateyoko -i
で不足フィールドを補って転置処理を実行すると、以下の出力が得られる。
$ cat triangle | tateyoko -i
1 3 7 8
_ 9 a 4
_ _ 6 2
_ _ _ 5
次に、 tateyoko -i
で転置した出力から、転置時に補った不足分レコードを削除する。
この処理の結果、各段の幅合わせがおこなわれ、 1 9 6 5
を軸として反転させたような、逆三角形の出力が得られる。
$ cat triangle | tateyoko -i | tr -d '_'
1 3 7 8
9 a 4
6 2
5
この逆正三角形を、 tac
で上下反転してみる。
すると、元の正三角形を反時計回りに一回転させた状態になった。
$ cat triangle | tateyoko -i | tr -d '_' | tac
5
6 2
9 a 4
1 3 7 8
ということで、正三角形に対して tateyoko -i | tr -d _ | tac
を実行すると、反時計回りに一回転出来る事が分かった。
後は、設問で求められている出力になるように、 tateyoko -i | tr -d _ | tac
でもう一回、反時計回りに回転させればOK。
通常版。
時計回りに一回転させた際の各段を、 awk
の連想配列を駆使して結合し、出力していく。$NF
は最下段、 $NF-1
は下から2段目、 $NF-2
は上から2段目、 $1
は最上段と考えて、それぞれ連想配列に入れていく。
最後に、幅合わせの処理と、正三角形になるようにレコードの反転を実行する。
#!/bin/sh
cat triangle | # 元テキストの出力
awk '{for(i=NF;i>=1;i--){st[NF-i]=$i" "st[NF-i]}} END{for(n in st){print st[n]}}' | # 右回転時の各段の出力
awk '{for(i=1;i<NR;i++){printf " "}; print $0}' | # 幅合わせの処理
tac # 上下の反転
以下、コピペ実行用のワンライナーコード。
Tukubai版。
cat triangle | tateyoko -i | tr -d '_' | tac | tateyoko -i | tr -d '_' | tac
通常版。
cat triangle | awk '{for(i=NF;i>=1;i--){st[NF-i]=$i" "st[NF-i]}} END{for(n in st){print st[n]}}' | awk '{for(i=1;i<NR;i++){printf " "}; print $0}' | tac
問6
特定の数が欠けている素数列に対して、欠けている数値部分で改行を実行する問題。
元の素数列に対して、1から100までの全ての素数を列挙した素数列と、 diff
で差分を取ってみる。
差分を取った結果から、改行すべき箇所を特定し、出力を整形してやればOK。
#!/bin/sh
cat prime | # 元データの出力
tr ' ' '\n' | # 列行変換
diff -y - <(primes 1 100) | # 1~100までの素数数列との差分を表示
awk '$0=$1' | # 第一フィールドのみの出力
xargs | # 行列変換
sed 's/\(> \)\{1,\}/\n/g' # 欠番素数部分の改行
以下、コピペ実行用のワンライナーコード。
cat prime | tr ' ' '\n' | diff -y - <(primes 1 100) | awk '$0=$1' | xargs | sed 's/\(> \)\{1,\}/\n/g'
問7
AAを表示するHTMLを、CLI上でAAとして出力する問題。
w3m
の -dump
オプションを使って、ブラウザ上で表示されるそのままの出力を、端末上に出力してやるだけ。
他の方の解答を見ていると、 nkf
の --numchar-input
オプションを使うのが、正当な正答らしい。
というか、 nkf
って、実体参照の復号化も出来るのか… 知らんかった。
#!/bin/sh
cat nyaan.html | # 元データの出力
w3m -T text/html -dump # HTMLコードの解釈結果の出力
以下、コピペ実行用のワンライナーコード。
cat nyaan.html | w3m -T text/html -dump
問8
AAから、空行・空列を除去する問題。
基本的な考え方は、通常版・Tukubai版どちらもほぼ同じ。
シェル上でのテキスト処理は、原則として、入力先頭からの逐次処理かつレコード単位 → フィールド単位の順番の処理となる。
(要するに、基本的に「上から下へ → 左から右へ」の流れでしか処理できない。)
したがって、縦方向に何らかの判定をおこなう(ある列が全て空白だったら○をおこなう○)といった処理は、非常に難しい。
そこで、縦方向に判定処理をするのではなく、出力を一度転置(縦横の入替)をしてやり、横方向として判定処理できるようにすると良い。
横方向での判定処理が完了したら、再度転置をして元の形に戻してやればOK。
福岡の著名シェル芸人・ぱぴろん( @papiron )さんも、同様の考え方で解答していらっしゃった。
#シェル芸 #福岡 Q8解答
— ぱぴろんちゃん👓 (@papiron) 2017年7月1日
$ cat shellgei | tr ' ' '-' | sed 's/./& /g;s/ $//' | rs -T | grep -v '^[- ]*$' | rs -T | tr -d ' ' | tr - ' '
#!/bin/sh
cat shellgei | # 元AAの出力
grep -v '^ *$' | # 余分な列の除去
awk -vFS= '{for(i=1;i<=NF;i++){str[i]=str[i]""$i}} END{for(i=1;i<=NF;i++){print str[i]}}' | # 行列転置
grep -v '^ *$' | # 余分な列(この処理時点では行)の除去
awk -vFS= '{for(i=1;i<=NF;i++){str[i]=str[i]""$i}} END{for(i=1;i<=NF;i++){print str[i]}}' # 行列転置
Tukubai版。
#!/bin/sh
cat shellgei | # 元データの出力
grep -v '^ *$' | # 空行の除去
tr ' ' '@' | # スペースを未使用文字に変換
awk -vFS= NF=NF | # 1文字1フィールドに変換
tateyoko | # 転置処理
grep -v '^[@ ]*$' | # 空行(転置前は空列)の除去
tateyoko | # 転置処理
tr -d ' ' | # 余分なスペースの除去
tr '@' ' ' # 未使用文字をスペースに変換
以下、コピペ実行用のワンライナーコード。
cat shellgei | grep -v '^ *$' | awk -vFS= '{for(i=1;i<=NF;i++){str[i]=str[i]""$i}} END{for(i=1;i<=NF;i++){print str[i]}}' | grep -v '^ *$' | awk -vFS= '{for(i=1;i<=NF;i++){str[i]=str[i]""$i}} END{for(i=1;i<=NF;i++){print str[i]}}'
Tukubai版。
cat shellgei | grep -v '^ *$' | tr ' ' '@' | awk -vFS= NF=NF | tateyoko | grep -v '^[@ ]*$' | tateyoko | tr -d ' ' | tr '@' ' '
参照Webサイト
jus共催 第29回激しいシェル芸勉強会 - Doorkeeper
→ 勉強会の概要。シェル芸 のライブ ストリーム(午後)
→ 今回の勉強会の動画配信(アーカイブ)。jus共催 第29回シェル芸勉強会 @さくらインターネットさん - Togetterまとめ
→ 当日の参加者の様子。第29回シェル芸勉強会へ遠隔参加 - 日々之迷歩
→ 大阪・福岡のサテライト会場、主催者さんの記事。第29回シェル芸勉強会まとめ
→ 勉強会の資料や参加記事等のリンク集。
雑記
今回は、暑さやら低気圧やらのためか、当日の朝から酷い頭痛になってしまい、遠隔ですら参加出来なかったorz
前回は、オーバーキルな難易度だったけれど、今回は比較的易しい問題が多かった印象。
最近、テキスト処理をする機会が少なくて、とても飢えていたので、シェル芸勉強会の問題は、とても良い気分転換になった (^q^)
勉強会を開催してくださった、上田会長をはじめとした日本UNIXユーザ会とUSP友の会の皆様、ありがとうございました!!