Checking Required Commands in Bash and a Discussion on Interfaces
Hello, this is Muno.
I've finished my last day at work. I received chocolates from a girl at the same workplace, was taken out for yakiniku just before my retirement, and had a fun time talking with a network engineer of 25 years about his past and current home network environments, while I shared stories about my home server setup.
I feel like I managed to do what I could as an inexperienced person, but since I'm a human with many shortcomings, I feel apologetic.
I feel every day that shell scripts are incredibly powerful.
Of course, their low portability means that functional descriptions, like those in csh which is the default shell in FreeBSD, are not possible, and this problem is undeniable.
However, I believe that the true strength of shell scripts lies in the ease with which they handle interfaces.
Interfaces in C and Shell Scripts
For example, let's say you use echo in a shell script as follows.
echo "test"
This corresponds to standard output in C language.
#include <stdio.h>
int main() {
const char *message = "test";
// 標準出力にメッセージを出力
fprintf(stdout, "%s\n", message);
return 0;
}
If you want to output to standard error, you would redirect as follows:
echo "testerr" 1>&2
And in the case of C language, it would be as follows:
#include <stdio.h>
int main() {
const char *message = "testerr";
// 標準エラー出力にメッセージを出力
fprintf(stderr, "%s\n", message);
return 0;
}
Furthermore, most GNU/Linux command execution failures are elaborately designed with return values thanks to past pioneers, making it easy to test conditions and quite effective if treated like a scripting language.
The problem, however, is that you can only benefit from this in environments where shell scripts are available.
Even if you write similar code in other languages, its versatility applies, but commands operate in a mechanism that corresponds to libraries in programming languages, and I feel that this is also why they are said to lack versatility.
However, I find the ability to manipulate file descriptors so simply to be extremely powerful.
Even else is just connecting two pipes.
*I thought about it after writing, but strictly speaking, it's not an else. ^^;
It just acts like an else when the return value is not 0.
$ curl aa || echo "wtf"
curl: (6) Could not resolve host: aa
wtf
How wonderful it would have been if I had known about such a convenient tool when I was in junior high school.
I wish any website had provided more opportunities to learn shell scripting.
In fact, now that I recall, the Japanese environment on GNU/Linux back then was quite challenging, and I remember that predictive text input for Japanese was not practical. So, perhaps the current ease is due to the proliferation of Chrome OS, which led to the flourishing of OSS Japanese input.
During my junior high school days, I also remember setting up a ThinkPad with Ubuntu for an eccentric friend who wanted it...
So, how do you check for commands?
When writing command checks in Bash shell scripts, I wondered if there was a smarter and more readable way.
That being said, passing commands from an array, which is inherently smart, to a for loop is certainly convenient, but looking at the syntax for array storage:
#!/bin/bash
cmds=(aa bb cc)
for i in ${cmds[@]}; do
command -v $i 1> /dev/null || echo "Please install $i"
done
Commands are stored in cmds as an array using (), and the array is expanded with ${cmds[@]}.
However, if you have to add to cmds=() every time a command is added, you start to wonder if it's even necessary to write it here.
In other languages, dependency information is packed into files like Cargo.toml, deno.json, go.mod, Gemfile, or the often-disliked requirements.txt.
It would be convenient to have a file that consolidates them in a similar way.
So, I tried something like this.
#!/bin/bash
cat ./cmd | xargs which 1> /dev/null || echo "Please install cmd"
And what does the cmd file look like?
$ cat cmd
ls
pwd
cargo
Actually, I wanted to check with the command command, but command runs as a shell built-in and could not be passed to 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)
I apologize if I'm mistaken, but I believe that when passed with xargs, it runs as a separate xargs process, distinct from the executing bash process, making it unrecognizable.
Therefore, I can't pass the failed command from which to Please install cmd, but since which's error output appears before that, I thought it might be unnecessary.
*I thought about it after writing, but maybe xargs /bin/bash command -v would work...
There's another advantage to putting this group of commands in a separate file.
You can install them all at once from this file.
cat cmd | xargs -I {} sudo pacman -S --noconfirm {}
For apt, it's similarly possible with the -y option.
So, whether I actually use it or not, I'll leave it here as an idea.
See you next time. Best regards. (It's probably just my imagination that I've written several articles since the year-end wrap-up post)