フォークせずに Bash で書かれた `ps aux`
(github.com/izabera)izabera/psは、新しいプロセスを作れない状況でも Bash の中でps auxに近い出力を再現するための Bash 実装 です- 中核となる前提条件は、
sshで接続したマシンに信頼できる bash shell はあるものの、ほかの PID がすべて使用中で新しいプロセスを生成できない状況です - README はこの状況を、Bash/Linux の知識が必要なポジション向けの 面接質問 の例として提示しています
- このツールは「動く
ps auxにアクセスできるふり」をできるようにすると表現されており、完全な代替実装であることを保証していません - 「すべてのマシンとあらゆる状況で 100% 動作する」という文は、明らかな ジョークめいた保証 として使われています
これは何をするプロジェクトか
ps auxを Bash だけで 書いたプロジェクトです- README のタイトルは「
ps auxwritten entirely in bash without ever forking」です - プロジェクトの中心的な特徴は、実行中に fork をまったく行わない点です
想定している状況
- 例として挙げられている状況は次のとおりです
sshでマシンに接続している- ユーザーは使い慣れた bash shell の中にいる
- しかし、ほかの PID がすべて使われており、新しいプロセスをまったく生成できない
- README では、この条件下で
ps auxに似た機能が必要になるかもしれないと紹介しています
期待できる範囲と caveat
- このツールは、動作する
ps auxがあるかのように "kinda sorta pretend" できるようにする用途として紹介されています - README の「100% のマシンであらゆる状況において完璧に動作、保証」という文言は、誇張された ユーモア として使われています
- したがって説明上の要点は、完全な互換性よりも、新しいプロセスを作れない極限環境で Bash だけを使って
ps auxを再現することにあります
1件のコメント
Hacker News のコメント
コンピュータサイエンスで一番難しい問題は結局 桁揃えだった、という冗談が妙に刺さる
いろいろな言語で列を揃える関数を数え切れないほど書いてきたが、毎回つらかったし、頭の中では「各列の最大長を求めて、タブ幅の次の倍数まで空白を入れればいい」くらい単純に見える
Python の f-string とパディング機能を使っても、コードはすぐ複雑で読みにくくなり、コメント用の例を書き直している最中にもバグをいくつも直したほどひどい
これほど一般的な用途なら当然ライブラリがあるはずだし、正直、標準ライブラリにないのは驚き
データベースカーソルの
descriptionから列幅と名前を取り出し、区切り線とフォーマット文字列を作ってから行を出力する方式だが、何か見落としている致命的なバグがあるのかは分からないし、特に難しい問題には見えないzip(*table)で列を転置して各列の最大長を求めた後、f"{r:<{w}}"で揃えて出力できる例の結果は
agony | kick | pumpのように列幅が揃った表になる値の中に空白が入り、空白でパディングされていて、ときどき揃えが崩れ、列からはみ出すこともある
いっそ互いに取り決めて、列揃えデータは使わず、もっと単純で人間が読める形式を使ったほうが全員にとって得になりそう
SSH で入ったマシンで Bash シェルは生きているのに全 PID が使い尽くされて新しいプロセスを作れないなら、
/proc/[pid]/ファイルシステムを探って、どのプロセスが PID 空間を枯渇させているのか確認すると思うBash の
killはシェル組み込みコマンドなので、/bin/killのように新しいプロセスを fork する必要がないPID を枯渇させる子プロセスを生み出している親プロセスを見つけられれば、それを止めてシステムの制御を取り戻せる
このスクリプトも
/procをパースしていて、新しい Bash サブシェルを作り得るパイプや$(...)置換もなく、かなりすっきりしているそうすれば別コマンドを実行しなくても必要な POSIX 関数を呼び出せるので、好反応だった
制限された環境で親プロセスを探して kill するより、再起動して復旧するほうが速いかもしれないし、PID が尽きているなら他の部分もすでに悪い状態になっている可能性が高い
ps(){ (cd /proc;for i in [0-9]*;do echo $i: $(tr '\0' ' ' < $i/cmdline);done); }/proc/[pid]/でどのプロセスが PID 空間を枯渇させているかを見るというのは正しいが、ソースコードのコメントを見ると、最初は/proc/*/statusだけで十分であることを期待していたものの、CPU 使用率のような値はそこから取得できなかったと書かれている[[ $cmdline ]] && exec {cmdline}>&-とexec {cmdline}< "$dir"/cmdline || continueがどう動くのか、本気で気になる2011 年に米国のかなり大きなテック企業で SRE ロールの面接を受けたが、当時は SRE という用語も初めて聞いた
その会社はブラウザベースの MS Office 代替を作っていて、電話スクリーニングの後は、同社の文書エディタ内で面接官と通話しながらリアルタイムにプログラミングしなければならなかった
自己評価表で
shell scriptingとLinuxを高めに書いて出したため、Bash でnetstatの代替を作る課題を受けたが、当時はソケット情報が/proc/のどこにどうあるのか分からず、すぐに無理だと判断した代わりに縮小版の
psとfuserを作ると提案し、あのひどいブラウザベースのワープロ内で出した解法が受け入れられてオンサイト面接まで進んだ今思うと、この練習問題の動機になっていた仮想シナリオは、思った以上に現実に根差していたのかもしれない
自分でもそこから始めると思う
SSH で入った状態で新しいプロセスを作れない状況を探索する インタラクティブな Web サイトを、以前遊びで作った: https://oops.cmdchallenge.com
"echo *"はディレクトリ内のすべてのファイルを列挙しない"echo .* *"を使う必要がある他のステージの
"View Solutions"一覧を見て、どんな別アプローチが可能だったのか確認したかったIzabera は #bash@libera の達人の一人
昔の freenode 時代から、そういう達人たちにはこの 10 年で本当に多くを学んだ
これはかなり きれいな Bash だ
経験上、Bash コードはたいていひどく書かれていて非効率なことが多いが、このコードはそうではない良い例に見える
Bash サポートのない信頼できる POSIX シェルに入っている場合はどうすればいいのか?
この Bash スクリプトは POSIX 互換ではない
このスクリプトは Bash 3.2 では動かないが、Bash 4.2 では動作する
Bash 3.2 では
printf: '(': invalid format characterエラーが出て、例の環境はbash-3.2-33.el5_11.4.0.1であるより良い用途は、procps がインストールされていないシステムでプロセス一覧を見ることかもしれない
悪くない
Bash で リスナーとクライアントも作ることはできる
実戦ではおすすめしない