bashで必要コマンドをチェックするときとインターフェースの話

5 min read

こんにちは、無能です。
職場の最終出勤を終え、同じ職場の女の子からチョコレートもらったり、退職ちょい前に焼肉連れて行ってもらったり、25年ネットワーク屋さんやっていた人と最後にその方の過去の自宅ネットワーク環境と現環境、自分は自分の自宅鯖周りの話したりと楽しく過ごしました。
出来る限り、未熟な自分の出来ることはなんとかやれた感あるかなとは思いますが至らない事が多いばかりの人間なので申し訳ない気持ちはあります。


シェルスクリプトは非常に強力だと日々感じます。
もちろん移植性の低さは確かに今FreeBSDのデフォルトシェルで存在するcshのように関数型の記述が出来なかったりしますしその問題は否めません。
しかし、実質的にシェルスクリプトはインターフェースの取り扱いの容易さにあると思っています。

Cとシェルスクリプトのインターフェース

例えば、シェルスクリプトで以下のようにechoをするとします。

echo "test"

これは、C言語に当たる標準出力にあたります。

#include <stdio.h>

int main() {
    const char *message = "test";

    // 標準出力にメッセージを出力
    fprintf(stdout, "%s\n", message);

    return 0;
}

もし、標準エラー出力に出力するのであれば以下のようにリダイレクトを行い以下となります。

echo "testerr" 1>&2

そして、C言語の場合以下にあたります。

#include <stdio.h>

int main() {
    const char *message = "testerr";

    // 標準エラー出力にメッセージを出力
    fprintf(stderr, "%s\n", message);

    return 0;
}

そして、大半のGNU/Linuxのコマンド実行時の失敗も戻り値は過去の開拓者のおかげで精巧に設計されており判定条件のテストもしやすくスクリプト言語のように扱うのであればかなり有効的です。
ただし問題は、これはシェルスクリプトが使える環境下で恩恵を受けれるというところです。
同じようなコードを他言語で書いたとしてもその汎用性は効きますがコマンドが言語で言うところのライブラリに当たるような仕組みで稼働している部分があり、その点も汎用性の乏しいと言われる所以だと感じています。
しかし、これだけ簡略的にファイルディスクリプタの操作を行えるというのは非常に強力だと私は感じます。

elseもパイプ二本繋げるだけです。 ※書いたあとに思いましたが、厳密に言えばelseではないですね^^;
戻り値0以外の時のelseのような動作をする、というだけでした。

$ curl aa || echo "wtf"
curl: (6) Could not resolve host: aa
wtf

こんなに便利なものを、中学生の頃にしれていたらどんなによかったことでしょう。
どんなサイトでももっとシェルスクリプトの学びの場を与えてくれていたらな、と思います。

というか今思い出せば、当時のGNU/Linuxの日本語環境は結構苦しくて日本語入力の予測変換が実用的とは言えないレベルだった記憶もあるのでChrome OS普及のおかげでOSSな日本語入力が栄えたからある今現代の楽さなのかもしれません。
当時の中学生時期は変わり者の友人がUbuntu入れたThinkPadを欲しいと言われて私がセットアップした記憶もありましたが・・・。

では、コマンドチェックはどうする?

コマンドチェックをBashシェルスクリプトに記述する際、スマートで見やすい書き方は無いかと思っていました。

とは言えど、元からスマートではある配列からループ処理に回すforで渡してあげるのはもちろん便利ですが配列格納時の記述を見ると。

#!/bin/bash
cmds=(aa bb cc)
for i in ${cmds[@]}; do
  command -v $i 1> /dev/null || echo "Please install $i"
done

()で配列としてcmdsに格納し、${cmds[@]}で配列の展開を行います。
しかしこれだとコマンドが増えた場合にcmds=()に追加しているとそもそもここに書く必要あるのか?という気持ちになってきます。
他言語だと依存関係はCargo.tomlやら、deno.jsonやら、go.modGemfile、嫌われがちなrequirements.txtだとかに情報が詰まっています。
そんな感じでまとめてあるファイルがあれば便利そう。

そんなわけでこんな感じにしてみました。

#!/bin/bash
cat ./cmd | xargs which 1> /dev/null || echo "Please install cmd"

そして、cmdファイルはどんな感じに?といいますと

$ cat cmd
ls
pwd
cargo

本当は、commandコマンドでチェックしたかったのですがcommandはシェルの内部コマンドとして動いておりxargsに渡せませんでした。

$ which command
which: no command in (/home/haturatu/.rbenv/bin:/home/haturatu/.rbenv/shims:/home/haturatu/.deno/bin:/home/haturatu/.cargo/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/opt/android-sdk/platform-tools:/var/lib/flatpak/exports/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/usr/lib/plan9/bin:/home/haturatu/.local/bin:/home/haturatu/.local/bin:/home/haturatu/Android/Sdk/cmdline-tools/tools/bin:/home/haturatu/Android/Sdk/platform-tools:/home/haturatu/Android/Sdk/emulator:/home/haturatu/Android/Sdk/tools:/home/haturatu/Android/Sdk/tools/bin:/home/haturatu/Android/Sdk/cmdline-tools/tools/bin:/home/haturatu/Android/Sdk/platform-tools:/home/haturatu/Android/Sdk/emulator:/home/haturatu/Android/Sdk/tools:/home/haturatu/Android/Sdk/tools/bin:/home/haturatu/.npm-global/bin:/home/haturatu/go/bin)

間違えていれば申し訳ないのですが、xargsで渡した場合は実行したbashプロセスとは別のプロセスとしてxargsのプロセスとして稼働するため認識出来なくなるものかと思われます。
なので、Please install cmdwhichで失敗したコマンドですを渡すこともできないですがその前時点でwhichのエラー出力は出るので不要かなと思いました。
※書いたあとに思いましたが、xargs /bin/bash command -vならいけるか…。

このコマンド郡を別ファイルにすると良いのがもう一つあります。
まとめてこのファイルからインストールできます。

cat cmd | xargs -I {} sudo pacman -S --noconfirm {}

aptの場合は-yオプションで同様に可能です。


というわけで実際に自分が使うかどうかは置いておいて、アイディアとして置いておきます。
それではまた。よろしくお願いします。(年末締めくくりの記事からなんか気づいたら数記事かいているのは気の所為である)

PGP --- Contact --- Machines