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.mod
、Gemfile
、嫌われがちな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 cmd
にwhich
で失敗したコマンドですを渡すこともできないですがその前時点でwhich
のエラー出力は出るので不要かなと思いました。
※書いたあとに思いましたが、xargs /bin/bash command -v
ならいけるか…。
このコマンド郡を別ファイルにすると良いのがもう一つあります。
まとめてこのファイルからインストールできます。
cat cmd | xargs -I {} sudo pacman -S --noconfirm {}
apt
の場合は-y
オプションで同様に可能です。
というわけで実際に自分が使うかどうかは置いておいて、アイディアとして置いておきます。
それではまた。よろしくお願いします。(年末締めくくりの記事からなんか気づいたら数記事かいているのは気の所為である)