第32回シェル芸勉強会の問題を解いてみたので、その内容を残しておく。 今回はちょっと用事が被って行けなかったので、現地でリアルタイムではやらずに後から家で問題を解いている。

問題、回答はこちら。 必要となるデータは、以下のコマンドでgithubから取ってくる。

git clone https://github.com/ryuichiueda/ShellGeiData.git

Q1.

echo 14679」から始めて、入ってない数字の箇所は空行として縦に出力するという内容。 とりあえず、オーソドックスにcommで処理をしてみる。

echo 14679 | grep -o . | comm <(cat) <(seq 9) --output-delimiter=',' | awk -F, '{print $3}'
blacknon@BS-PUB-UBUNTU-01:~$ echo 14679 | grep -o . | comm <(cat) <(seq 9) --output-delimiter=',' | awk -F, '{print $3}'
1

4

6
7

9

で、他の人の回答も見てみたのだけど、@ebanさんの回答が相変わらず尖っていた。 trコマンドで、パイプで渡した14679以外の数字をseqの出力から削除するという方法を取っているようだ。

Q2.

Q1の続きで、さらに空行の箇所をアルファベットにして出力するという内容。 ひとまず、以下のようにsedで続けて記述して対応した。

echo 14679 | grep -o . | comm <(cat) <(seq 9) --output-delimiter=',' | awk -F, '{print $3}' | sed -z -e's/\n\n/\n'{a..z}'\n/'
blacknon@BS-PUB-UBUNTU-01:~$ echo 14679 | grep -o . | comm <(cat) <(seq 9) --output-delimiter=',' | awk -F, '{print $3}' | sed -z -e's/\n\n/\n'{a..z}'\n/'
1
a
b
4
c
6
7
d
9

ちなみに、@ebanさんの上の流れでの回答がこんな感じだった。 trコマンドを使って除外する数字をアルファベットに置換してマージさせているようだ。

Q3.

「/etc/service」から、ポート番号が素数になっているサービスのみを抽出するという問題。 基本的にポート番号は65535以上はないので、速度が出ないけど最初に素数のリストを作ってそれをベースに抽出するという方法もあるが、それだと速度が異常に遅くなってしまう。

このため、/etc/serviceから該当箇所だけを取り出して素数かどうかを判定し、出力させるようにする。 awkで取り扱うため、一度sedで区切り文字を統一させる。 素数はawkで出せればよかったのだけど、長くなりそうだったのでOSのコマンドを使ってる。

sed -r 's/\s+/,/g' /etc/services | awk -F'[,/]' '/tcp|udp/{"factor "$2"|awk NF==2"|getline v;if(v!="")print $1;v=""}'

Q4.

下のような3×3の数字を、ジグザグ順に一列に出力させる(974128356)という内容。

136
725
948

要は、逆さにして9を頂点にした45度のひし形にしてやると考えればいいということか。 個人的な好みの関係から、こういうときは列をずらしてdatamashで縦横を変換して出力させることで対応することが多いので、今回もその方法で対処する。

tac nums.txt | sed 's/\B/,/g;s/.$//g' | awk '{print a$0;a=a","}' | datamash transpose -t, --no-strict | sed -r 's@(N/A|,| )@@g;' | rev | grep -o .
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.32$ tac nums.txt | sed 's/\B/,/g;s/.$//g' | awk '{print a$0;a=a","}' | datamash transpose -t, --no-strict | sed -r 's@(N/A|,| )@@g;' | rev | grep -o .
9
7
4
1
2
8
3
5
6

Q5.

ファイルから、ウムラウト(ドイツ語とかでアルファベットの母音の上に書かれる発音記号。 トレマともいうらしい)を含む単語のみを抜き出すという問題。 今回は、PerlのUnicodeスクリプトを使って対象の単語を抽出してみる。 「InLatin-1_Supplement」でウムラウトを含む文字が抽出できるのだが、なぜかカタカナや漢字も抽出されてしまったので事前に除外してやる。

cat umlaut.txt | xargs -n1 | grep -P -v '\p{Katakana}|\p{Han}' | perl -lne 'print $_ if /\p{InLatin-1_Supplement}/'
blacknon@blacknon-ThinkPad-X201:~/ShellGeiData/vol.32$ cat umlaut.txt | xargs -n1 | grep -P -v '\p{Katakana}|\p{Han}' | perl -lne 'print $_ if /\p{InLatin-1_Supplement}/'
wäschst
Schrödinger
Ö
Übel
Ärztin

その他、やはり@ebanさんのやり方が凄かった。 1個めはgrepでUnicodeのコードでxc3から始まるコードを指定する方法、2個めはawkで2バイトの文字を含む単語を抽出することで対応しているようだ。

Q6.

Twitterで、特定のアカウントがつぶやくたびに「んほぉ!」と端末に表示させるという問題。 表示させる文字列が非常に気になる( エロゲではないのかこれは )のはおいといて、どのようにして取ってくるか。 スマートなのはRestAPIを叩いて取得してくる方法だと思うのだけど、最近はAPIの利用に認証が必要になってしまったため少々使いにくい。 で、Ctrl+Cとか受けつけないのでちょっと扱いにくいのだけど、curlでtwitterの総ツイート数の箇所を抜き出して、それをwatchで監視して変化があったら「んほぉ!」と出力させる処理をwhileで無限ループさせることで対処してみた。

while :;do watch -g "curl -s https://twitter.com/@blacknon_/ | grep -Eo '\"[0-9,]+ツイート\"'";echo んほお!;done

Q7.

ファイル「image.txt」について、以下の処理をする。

  • ある数字について、上下左右の数字どれか1つに0が含まれる場合は0、そうでなければ1にする。
  • 次に、上下左右の数字どれか1つに1が含まれる場合は1、そうでなければ0にする

エライのが出てきたなー…(´・ω・`)。ムズい。 awkでゴリゴリ書くくらいしか思いつかないなー、と思ったのだが、@ebanさんがImageMagicを使って解くという方法を取っていた。 こんなことできるのか。

Q8.

japanese.txtで、行頭に漢字かカタカナが来るように改行を入れる。 ただし、「シェル芸」のようなカタカナ+漢字のものは単語として扱い、改行を入れないようにするという問題。perlのUnicodeスクリプトをうまく利用する必要がありそうだ。

cat japanese.txt | perl -C -lpe 's/(\p{Katakana}+\p{Han}|\p{Han})/\n\1/g;' | sed '/^$/d'
blacknon@BS-PUB-UBUNTU-01:~/ShellGeiData/vol.32$ cat japanese.txt | perl -C -lpe 's/(\p{Katakana}+\p{Han}|\p{Han})/\n\1/g;' | sed '/^$/d'
ん
僕らは
既に
死んでいる
死んでいるから
シェル芸だ。

多少は解けたかなーと思うけど、大体いつも通り他の人の回答を見て学ぶことの方が多かった。 特にQ1,2の@ebanさんの回答は個人的に目からうろこだった。