たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

第48回シェル芸勉強会に参加しました

しました

今回は前回に続きリモート開催ということで、おうちからYouTube越しに参加。サテライトとは違い自分のうめき声しか聞こえないのでちょっと寂しいですね

問題と解答例はここ。今回はbashというかプロセスが云々の問題が多いようなので、PowerShellの解答はすくなめ

Q1

以下のようなスクリプトlinenoがあります

echo $LINENO

これを使って、呼び出し元のLINENOを出力する問題。

そもそも$LINENOは、行番号が格納されている変数。なので、これを普通に実行すると 1 が出力されて終了する。コマンドラインからecho $LINENOすると、現在操作しているシェルの行番号が出力される。これをスクリプトを使って出力したい。

$ eval "$(cat ./lineno)"

はい。読みだしてそれをevalすればよい。cat を使わない方法としては

$ eval "$(< ./lineno)"

と書けるらしい。便利。

Q2

echo ${!BASH*} とすると、BASHで始まる変数が展開される一覧にある BASH_VERSION の中身を出力する問題。

$ echo ${!BASH*} | awk '{print $NF}' | xargs -I@ bash -c "echo \${@}"

最初に答えたのはこれ。$BASH_VERSION は最後に現れるので、$NFxargsechoを作ってbashに渡す。これを別の書き方をすると

$ echo ${!BASH*} > /dev/null && eval "echo \${$_}"

${!BASH*} は展開されるので $_BASH_VERSIONが入る。echo \${$_} という文字列を作る。これはecho ${BASH_VERSION}になるのでevalすれば欲しい文字列が得られる。

$ echo ${!BASH*} > /dev/null && echo ${!_}

TL眺めてていいなあと思ったのはこれ。${!_}で変数関節展開をして出力する。最中は思いつかなかったなぁ。

Q3

sleepをプロセス番号1のプロセスの下に3つぶら下げる問題

これは最中に解けなかった。やり方としては、子プロセスより親プロセスが先に死ぬと、子プロセスが親プロセスの親プロセスにぶら下がるようになるのを利用する。つまり、sleep より先に呼び出した bash が死ぬようにすればいい。

$ ( sleep 100 | sleep 100 | sleep 100 & )

sleep をバックグラウンドで動かしておく。呼び出し元のサブシェルはそのまま終了するのでsleepinitとかにぶら下がるようになる。ということらしい

Q4

今使っている端末上でecho $$して1を出力する問題。$$はプロセス番号を表す。なのでinitとかsystemdになろうって感じかなあ。と思ったらそういうことではないらしい

$ sudo unshare --fork --pid --mount-proc bash -c 'echo $$'

unshareを使って名前空間を分割。そこで bash -c 'echo $$' を実行すると、分割された名前空間bashがプロセス番号1として起動するので echo $$ で1が出力される。名前空間の分割はDockerでも使われている技術。SD誌を読もう!!!!!!

Q5

勝手にプロンプトにunkoと入力されるようにする問題。

なんだこの問題…と思っていたら時間が終わってた。解答としては exec でSTDINを奪うというもの。killしないと止まらないので危険シェル芸

Q6

$SHLVLseqコマンドを作る問題。

$ echo 'echo $SHLVL; bash -c "$0"' > s
$ chmod +x s
$ ./s | head

echo $SHLVLで数字を出しつつ、$0再帰する。うむ

Q7

sleepを三代ぶら下げる問題。Q3の応用

$ ( ( ( sleep 1000 ) & exec sleep 1000 ) & exec sleep 1000 ) &

なんでできたかわからん。簡単に言うと

sleep 100 &
exec sleep 100

というようなシェルスクリプトを実行すると

bash
 + sleep
    + sleep 

という風になる。このうち真ん中のsleepexecbashからsleepに変えられたもの。これを三段にするなら、これを呼び出すやつが呼び出したあと、exec sleepすればよい

bash -c 'sleep 100 & exec sleep 100' &
exec sleep 100

これでOK。これをサブシェルを使ってワンライナーにしたのが上の解答ということ

Q8

プロセスツリーで二分木を作る問題。forkbombに怯えてしまったのでやらなかったけど、実はこの問題が一番シェル芸っぽい問題とのこと

二つのプロセスを作るには

(bash|bash)

とすればいい。これを繰り返せばいいので以下のようにする

$ echo bash{,,,} | sed 's/bash/(&|&)/g' | sed 's/ /|/g;asleep 1000'
(bash|bash)|(bash|bash)|(bash|bash)|(bash|bash)
sleep 1000

これを bash に評価させる。この時&しておく。

$ echo bash{,,,} | sed 's/bash/(&|&)/g' | sed 's/ /|/g;asleep 1000' | bash &
$ ps --forest
  339 pts/0    00:00:00  \_ bash
  400 pts/0    00:00:00      \_ bash
  401 pts/0    00:00:00      |   \_ bash
  403 pts/0    00:00:00      |   |   \_ bash
  413 pts/0    00:00:00      |   |   |   \_ sleep
  404 pts/0    00:00:00      |   |   \_ bash
  402 pts/0    00:00:00      |   \_ bash
  405 pts/0    00:00:00      |   |   \_ bash
  407 pts/0    00:00:00      |   |   \_ bash
  406 pts/0    00:00:00      |   \_ bash
  410 pts/0    00:00:00      |   |   \_ bash
  411 pts/0    00:00:00      |   |   \_ bash
  408 pts/0    00:00:00      |   \_ bash
  409 pts/0    00:00:00      |       \_ bash
  412 pts/0    00:00:00      |       \_ bash

おお、forkbombに怯えることなく二分木が出来た。すごいぜこれは

LT

今回は参加しませんでした。配信設備がない…ぐぬぬ。まあネタもなかったんですが…

LTをしてくださったお二人はどちらも超良かったのでぜひアーカイブをご覧ください。

終わりに

前回はフル参加できなかったので久しぶりのシェル芸勉強会でした。いつもとは少し毛色が違う問題があってとても楽しかったです!ありがとうございました!!!