はじめに
第63回シェル芸勉強会(2023年2月25日開催)の問題と回答から、シェル芸について学ばせていただこうという記事です。よろしくお願いいたします。
本記事の回答例は、シェル芸勉強会の配信で紹介されていたワンライナーを引用させていただいております。より詳しい内容は配信を参照してくださいませ。
また、シェル芸はすべて「websh」で実行しました。webshに多大なる感謝を。
配信
Q1
問題文
九九の答えの合計を出力
回答例(配信から引用)
echo {1..9}*{1..9} | tr ' ' '\n' | bc | awk '{a+=$1}END{print a}'
Q1の勉強
echo {1..9}*{1..9}
ブレース展開を利用して1*1~9*9までの九九を作ります。
tr ' ' '\n'
スペース区切りを改行区切りに置換して、処理をしやすくします。
bc
九九をすべて計算します。
awk '{a+=$1}END{print a}'
awkで九九の計算結果をすべて足し合わせて、最後に出力します。
Q1の感想
おそらくシェル芸の基礎を問う問題だと思うのですが、私にはそこそこ難しく感じました。特にawkはまだ理解できていないことが多々あるので、最低限でも良いので使いこなせるように勉強したい所存です。あと、ブレース展開はとても便利ですね。
Q2
問題文
10000円-9132円のおつりの出し方を出力
回答例(配信から引用)
f () { awk -v x=$1 'NR==1{print $1%x;print int($1/x),x}NR>1'; };
echo 10000-9132 | bc | f 500 | f 100 | f 50 | f 10 | f 5 | f 1
Q2の勉強
f () { awk -v x=$1 'NR==1{print $1%x;print int($1/x),x}NR>1'; };
まず、関数を用意します。
x=$1
引数をxに格納します。xは硬貨の値です(500や100、50、など)。
print $1%x
おつりの合計(余り)をxで割ったときの余りを出力します。
print int($1/x),x
おつりの硬貨の枚数と硬貨の値(x)を出力します。
echo 10000-9132 | bc | f 500 | f 100 | f 50 | f 10 | f 5 | f 1
用意した関数を使って、おつりの硬貨の枚数を出力します。大きい硬貨(500円玉)から処理を行う必要があります。
Q2の感想
とてもおもしろい解き方だと感じました。おつりの出し方のような問題は競技プログラミングでも頻出しますが、シェル芸勉強会でも出題されるのだと分かって感動いたしました。
AtCoderの似たような問題を紹介しておきます。
https://atcoder.jp/contests/abc087/tasks/abc087_b
Q3
問題文
LaTeX原稿から、\labelと\refがセットになっていないものを出力
回答例(配信から引用)
cat S*a/v*63/g*u.tex | grep -oE '\\(ref|label){[^\}]*}' | tr '\{}' ' ' | sort -k 2,2 | uniq -f 1 -u
Q3の勉強
grep -oE '\\(ref|label){[^\}]*}'
\ref{…}と\label{…}だけを抽出します。grepのoオプションは検索にマッチした部分だけを抽出、Eオプションは拡張正規表現を使えるようにするらしいです。
tr '\{}' ' '
trで\{}をスペースに置換します。
sort -k 2,2
sortのkオプションはキーを指定してソートを行います。各行の2列目の要素だけでソートを行う場合は2,2と書くようです。
uniq -f 1 -u
uniqのfオプションは指定した列以降を評価するようです。uオプションは重複した行を出力しないようです。つまり、セットになっていない\labelと\refの行だけが出力されます。
こちらの記事を参考にさせていただきました。
https://atmarkit.itmedia.co.jp/ait/articles/1604/07/news018.html
Q3の感想
grepとsortとuniqというシェル芸の代表的なコマンドたちが躍動していて素晴らしい問題だと思います。とても勉強になりました。特に、uniqのuオプションを知ることができて良かったです。
Q4
問題文
東西南北がセットになっているところを探索(ただし、東西南北の順番は問わない)
回答例(配信から引用)
cat S*a/v*63/t*n.txt | awk '{for(i=1;i<=length($0);i++)print i,substr($0,i,4)}' | grep 東 | grep 西 | grep 南 | grep 北 | awk '{printf $1" "}'
Q4の勉強
awk '{for(i=1;i<=length($0);i++)print i,substr($0,i,4)}'
awkで文字列を4文字ずつ切り出します。
grep 東 | grep 西 | grep 南 | grep 北
grepを4回繰り返して、「東西南北」ワンセットになっている行だけを抽出します。
Q4の感想
grepを4回使って東西南北がワンセットになっていることを判定するという考え方が素晴らしいです。私もこのような方法を自力で思いつけるようになりたいと思いました。
Q5
問題文
リバーシの盤面にxを表示
12345678
A
B
C ⚪
D ⚪⚫
E ⚫⚫
F ⚪⚫
G
H
回答例(配信から引用)
cat S*a/v*63/r*i.txt | sed -zE 's/(⚪.{10}(⚫.{10})+) /\1✖/g' | sed -zE 's/(⚪.{10}(⚫.{10})+) /\1✖/g'
Q5の勉強
sed -zE 's/(⚪.{10}(⚫.{10})+) /\1✖/g'
sedのzオプションはnullで行を分割するので、改行を無視できるらしいです。Eオプションは拡張正規表現です。盤面のスペースは全角です。
⚪.{10}
⚪の後に何かしらの文字が10文字あるということを表しています。
(⚫.{10})+
⚫の後に何かしらの文字が10文字あり、+は直前のパターンの1回以上の繰り返しを表しています。
こちらの記事を参考にさせていただきました。
https://atmarkit.itmedia.co.jp/ait/articles/1610/17/news015.html
Q5の感想
正規表現は難しいです。しっかり使いこなせるように、たくさん問題を解いていきたいですね。勉強になりました。ありがとうございます。
Q6
問題文
Q5のワンライナーに続けて、⚪と✖の間の⚫を⚪に置換
回答例(配信から引用)
cat S*a/v*63/r*i.txt | sed -zE 's/(⚪.{10}(⚫.{10})+) /\1✖/g' | sed -zE 's/(⚪.{10}(⚫.{10})+) /\1✖/g' | tr -d \\n | perl -CSD -Mutf8 -pe 's/⚫(?=.{9}(⚫.{9})*✖)/⚪/g' | fold -b27 | awk 4
Q6の勉強
tr -d \\n | ... | fold -b27
trで改行を削除して、foldで27バイトごとに改行で分割しているようです。
perl -CSD -Mutf8 -pe 's/⚫(?=.{9}(⚫.{9})*✖)/⚪/g'
-CSDと-Mutf8をつけるとUTF-8を扱えるらしいです(全角文字を扱うときなどに有効とのこと)。eオプションはperlのスクリプトとして実行、pオプションはスクリプトを繰り返して実行するらしいです。
s/⚫(?=.{9}(⚫.{9})*✖)/⚪/g
⚫を⚪に置換しています。⚫(?=.{9}(⚫.{9})*✖)は、⚫の後に何かしらの文字が9文字あり、その後に「⚫と何かしらの文字が9文字」のセットが0回以上繰り返していて、そして、その後に✖がある、ということを表しています。
こちらの記事を参考にさせていただきました。
https://atmarkit.itmedia.co.jp/ait/articles/1712/07/news013.html
https://qiita.com/doikoji/items/166725ecea46110c402f
https://www.tohoho-web.com/perl/app2.htm
Q6の感想
人生で初めてperlコマンドを使用しました。perlの正規表現はとても強力だという噂は聞いたことがあったので、実際に使うことができて良かったです。勉強になりました。ありがとうございます。
おわりに
人生初のシェル芸勉強会でした。たくさんのことが学べたので、とても有意義な時間となりました。これからも、さらに精進してシェル芸の力を身につけていきたいと思います。また、次回のシェル芸勉強会も参加させていただきたいと考えております。よろしくお願いいたします。書籍『シェル・ワンライナー160本ノック』も読み進めていきますので、そちらもよろしくお願いいたします。この度は誠にありがとうございました。
コメントを残す