「第19回シェル芸3周年記念勉強会」記録――「一億総シェル芸社会」に向けて

はじめに

10月31日(土)に開催された「第19回シェル芸3周年記念勉強会&第36回だいたい発足6年半記念USP友の会定例会」の大阪サテライト「第19回シェル芸勉強会 大阪サテライト」に参加しました。

当日の様子については、大阪サテライトを主催されたくんすと氏によるレポート「「第19回シェル芸勉強会 大阪サテライト」レポート - くんすとの備忘録」をご覧になると良いと思います。

また、上田会長による問題と模範解答が「[【問題と解答】第19回シェル芸3周年記念勉強会 上田ブログ](https://blog.ueda.asia/?p=7068)」に、Togetterまとめが「第19回シェル芸3周年記念勉強会 - Togetterまとめ」にありますので、これらもあわせてご覧ください。

なお他の地域では、福岡にて「福岡サテライト会場:第19回シェル芸勉強会」がカツニャリ氏の主催で開催されました(まとめをご覧になりたい方は「第19回シェル芸勉強会へ遠隔参加 - 日々之迷歩」をどうぞ)。

それでは、

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

してスタートです。

注: テーマおよび難易度の表記はあくまでも個人の主観です。


Q1(bashの泥沼――その1):

$ [Shell one-liner]
1ppm
$ cat A1.sh
#!/bin/bash
[Shell one-liner]
exit 0
$ ./A1.sh
40ppm

になるような「[Shell one-liner]」の部分に該当するシェル芸を考えます。

*追記(2015-11-06): 上記「[Shell one-liner]」の部分(計3ヶ所)について、Masaki Waga氏による指摘のツイートを受け、分かりやすさを向上すべく「__foo bar__」から変更しました。Masaki Waga氏に対し、改めてお詫びならびに感謝申し上げます。*

A1(難易度★★★☆☆/解答成功):

bashの特殊変数$0の値を利用します。

この$0には通常、シェルスクリプトのファイル名が入っていますが、端末から直接読んでみると

$ echo $0
/bin/bash

といった結果が返ってくることから

$ if [ $0 = "/bin/bash" ] ; then echo 1ppm; else echo 40ppm; fi
1ppm
$ cat A1.sh
#!/bin/bash
if [ $0 = "/bin/bash" ] ; then echo 1ppm; else echo 40ppm; fi
exit 0
$ ./A1.sh
40ppm

のように分岐処理で解答してみました。

Q2(数字の操作):

$ echo 1 4 | foo
4
3
2
1
2
3
4

というふうに、2つ目の数字から1つめの数字へとカウントダウンして再び2つめの数字に戻るようにします。

A2(難易度★★☆☆☆/解答成功):

シンプルにawkでループ処理してみました。

$ echo 1 4 | awk 'END{for(n=$2;n>$1;n--){print n};for(n=$1;n<=$2;n++){print n}}'
4
3
2
1
2
3
4

Q3(マイナンバーシェル芸?):

正規表現(ERE)で表すと

^([0-9][0-9][0-9]1234567890 [0-9][0-9]1234567890[0-9] [0-9]1234567890[0-9][0-9] 1234567890[0-9][0-9][0-9])$

になる数字を列挙します。

A3(難易度★☆☆☆☆/解答成功):

bashのブレース展開を使えばOKです。

$ echo {0..9}{0..9}{0..9}1234567890 {0..9}{0..9}1234567890{0..9} {0..9}1234567890{0..9}{0..9} 1234567890{0..9}{0..9}{0..9} | tr ' ' '\n'
0001234567890
0011234567890
0021234567890
(...中略...)
1234567890997
1234567890998
1234567890999

最後の「tr ‘ ‘ ‘\n’」はxargs -n1でも構いませんが、trを使ったほうが速いと思います。

Q4(改行があっても変換):

ファイルQ4内にある「すっとこどっこい」を途中の改行を無視して「朴念仁」に変換します。

変換前の内容は次の通りです。

$ cat Q4
この
すっとこどっ
こい
すこっと
どっこい
すっとこすっとこど
っこい
どっこいどっこい
すっとこどっこん
すっ
とこ
どっ
こい

A4(難易度★★☆☆☆/解答成功):

あらかじめtrで改行文字を他の適当な文字に置き換えてからsedで変換する方法をとりました。

$ cat Q4 | tr '\n' '_' | sed 's/す_\?っ_\?と_\?こ_\? ど_\?っ_\?こ_\?い/朴念仁/g' | tr '_' '\n'
この
朴念仁
すこっと
どっこい
すっとこ朴念仁
どっこいどっこい
すっとこどっこん
朴念仁

sedコマンド内の正規表現で使っている「直前の項目が0回または1回マッチする」繰り返し演算子「?」については、「直前の項目が0回以上マッチする」という「*」に替えたほうが連続した改行にも対応できてより良い答えになったかと思います。

また、別解としてsedの新しめのバージョンにある-zオプション

-z, –null-data
  NUL 文字で行を分割する

を使った

$ cat Q4 | sed -z 's/す\n*っ\n*と\n*こ\n*ど\n*っ\n*こ\n*い/朴念仁/g'
この
朴念仁
すこっと
どっこい
すっとこ朴念仁
どっこいどっこい
すっとこどっこん
朴念仁

というシンプルなシェル芸もあります。

Q5(Webスクレイピング):

データURIスキームによって「[experiment 上田ブログ](https://blog.ueda.asia/?page_id=7123)」に埋め込まれた、USP友の会マスコットキャラクターのちんじゅうちゃんの画像を抜き出して保存します。

A5(難易度★★★☆☆/解答成功):

Webブラウザ上で画像にマウスポインタを置いて右クリック……ではなく、curlでHTMLファイルを取り込みgrepsedで不要な部分を取り除いた後にbase64でデコードすることでファイルとして保存できます。

$ curl -s https://blog.ueda.asia/?page_id=7123 | grep '<img src="data:image/png;' | sed 's/.*base64,\(.*==\)".*/\1/' | base64 -d > A5.png

Q6(2進数から日本語に):

日本語(Shift JIS)を2進数にしたファイルQ6

$ cat Q6
10001010011001011001001001101110(...中略...)10000001010000100000110100001010

を元の日本語に戻します。

A6(難易度★★★★☆/解答失敗):

当日はxxdで解こうとしましたが、上手くいかずに時間切れになってしまった問題です。

nkf–url-inputオプション

–cap-input, –url-input
  それぞれ :、% に続く 16 進数を文字に変換する

があることを活かした、まずbcsedで2進数からURLエンコードしたデータを作成し、それをnkfに渡すという方法があります。

$ echo 'obase=16;ibase=2;'$(cat Q6) | bc | tr -d '\n\\' | sed 's/\(..\)/%\1/g' | nkf --url-input -w
各地に多種多様な賭博が存在する。
特に有名なものは野球賭博である。

別解として、2進数のデータを8桁ごとにperlpack関数に与えていく方法もあります。

$ perl -ne 'print pack("B8",$_)' <(cat Q6 | fold -w8) | nkf -w
各地に多種多様な賭博が存在する。
特に有名なものは野球賭博である。

Q7(bashの泥沼――その2):

現在使っているbashの深さを保持している特殊変数$SHLVLについて、

$ SHLVL=100; echo $SHLVL
100

のような$SHLVLに「100」を代入しない真っ当(?)な方法で「100」を出力します。

A7(難易度★★★★★/解答失敗):

個人的感想をいえば、今回の勉強会で最も難しい問題でした。

基本的には

$ echo $SHLVL
3
$ cat A7-TEST
bash
bash
(...中略...)
bash
bash
echo $SHLVL
$ cat A7-TEST | bash
100

のように、最終行に「echo $SHLVL」とある以外は各行に「bash」とだけ書いてあるファイルA7-TESTの内容に相当するデータを生成してbashに渡せば解決できるのですが、現在のbashの深さに合わせて「bash」の行を生成しておく必要があります。

必要な「bash」の行数 = 希望する$SHLVLの値 - 現在の$SHLVLの値 - 最後にある「echo $SHLVL」の1行分

ということから、$SHLVLの値が100になるようなファイルA7-TESTの内容に相当するデータをワンライナーで生成することができます。

$ (yes bash | head -n $((100-$(echo $SHLVL)-1)); echo 'echo $SHLVL')
bash
bash
(...中略...)
bash
bash
echo $SHLVL

そして、この生成したデータをbashに渡せば完了です。

$ (yes bash | head -n $((100-$(echo $SHLVL)-1)); echo 'echo $SHLVL') | bash
100

Q8(bashの泥沼――その3):

危険シェル芸の代名詞ともいえる

$ : (){ : | : & }; :

を改良(?)し、1,000プロセスほど立ち上がったところで安全に止めてみます。

A7(難易度★★★★☆/解答失敗):

いわゆる「フォーク爆弾」を止めるには、使用できるプロセス数に制限をかければ良い、ということでbashの組み込みコマンドulimitを利用します。

ulimitで使用可能プロセス数に制限をかけるには-uオプションを使いますが、同時に-Sオプションを指定しておかないとrootユーザ以外はいったん減らしたプロセス数を元に戻せなくなるのでその点には注意が必要です。

$ ulimit -Su 1000; : (){ : | : & }; :
bash: fork: retry: 子プロセスがありません
bash: fork: retry: 子プロセスがありません
(...中略...)
bash: fork: retry: 子プロセスがありません
bash: fork: リソースが一時的に利用できません
(...中略...)
bash: fork: リソースが一時的に利用できません
bash: fork: リソースが一時的に利用できません

[1]+  終了                  : | :

[Enter]を打つと無事に止まっていることが分かります。

終わった後は

$ ulimit -Su 15997

で元に戻します。

おまけ(Doomシェル芸):

勉強会後のビアバッシュとLTの時にくんすと氏がツイートされた(ありがとうございます)「Brutal Doom modの自動照準モードを有効にするパッチを当てるシェル芸」内の画像にあるシェル芸は次のようなものでした。

$ PK3='brutalv20' ; unzip ${PK3}.pk3 -d tmp && grep -lr '^[ \t]*+WEAPON\.NOAUTOAIM' tmp | xargs sed -i 's#^\([\t ]*\)\(+WEAPON\.NOAUTOAIM\)#\1//\2#' ; grep -lr '^[\t ]*[^/]*A_SetPitch' tmp | xargs sed -i 's#^\([\t ]*\)\([^/]*A_SetPitch\)#\1//\2#' ; (cd tmp ; zip -r ../${PK3}-autoaim.pk3 .) && rm -fR tmp

この長いワンライナーの手順を大まかに説明すると、

  1. ゲームデータを格納しているPK3ファイルをunzipで解凍
  2. sedACSスクリプトファイル内の「+WEAPON.NOAUTOAIM」をコメントアウト
  3. zipで再度圧縮

といった感じになります。

ちなみにこのシェル芸は、シェル芸人を目指す者にとっての必読書とされている(?)上田会長の著書『シェルプログラミング実用テクニック』の「6.4 オフィススイートとの連携」から思い付きました。


おわりに

開催場所のUmidass安楽寺の中にあるということで、本当の「sh行」になってしまいました。

残念ながら当日リアルタイムで後半のQ6〜Q8を解答することはできませんでしたが、宿題として解いてみるとbashまわりでの新たな発見などがあって良い勉強になりました。

最後に、上田会長ならびに大阪サテライト主催のくんすと氏をはじめとする参加者の皆さん、お疲れ様でした。

Written on November 4, 2015