It is frequently asserted that "the UNIX philosophy" is to build complexity out of simpler, reusable parts.
This has resulted in a rich toolbox to build shell scripts out of.
There is not always an available command to use in a shell script, so you might need to write something yourself to fill the gap; or you might have written a program that does too many things, and you want to split it up into independenly reusable parts.
So there's a few recommendations for different styles of programs.
Data processing programs
This means programs like grep, sort and cut.
Programs should be able to take multiple records of input, and produce multiple records of output.
They should read their input records from the standard input stream, and write it to the standard output stream, so that it can be put into shell pipelines.
Context managing programs
This means programs like chroot, flock, sudo, systemd-inhibit and unshare.
These do some operation, then run a follow-up command in the new context. With chroot the new command is run, rooted in a different subtree; with flock the new command is run with a lock on a file taken; with sudo the new command is run with a different user; with systemd-inhibit the new command is run with an inhibitor lock taken; and with unshare the new command is run with a different namespace.
As continuations
These programs should take a list of arguments to run instead of a shell command
so that they don't require the command to be evaluated as a shell command,
since that requires the program to run the shell,
is extra effort to secure the command to prevent command injection,
and can be turned into a shell command by running sh -c "$COMMAND" -
.
If your command needs to provide the subcommand with more context then having the entirity of the remaining arguments be the command makes it difficult to include this context as an argument.
These commands should clean up any resources provided as the context when the subcommand exits.
For chroot, flock, sudo and unshare the context is tied to the subprocess' lifetime so this is easy.
With other cleanup
Avoid programs that need you to provide a continuation command where possible, since it is not easy to distinguish where the command may have failed.
You can instead clean up with a trap, such as the following command which has a temporary directory.
td="$(mktemp -d)"
trap 'rm -rf "$td"' EXIT
some_command "$td"
Since flock's context is tied to a file descriptor, which may be inherited to subprocess, instead of using the continuation form, you can tell it which file descriptor to operate on instead.
(
flock 100
some_command
) 100<lockfile
Passing context to subcommands
For commands that are both bound to the process' context and need to inform the subprocess of what it is, you can tell it where to write the information to, such as an environment variable or file.
I like telling a command to write information to a FIFO (as I do with ephemeral-launch) since it also allows for some program synchronisation, as it blocks until you read the port it bound to out of the FIFO.
inetd prefers to include the address of the port that was bound
in the TCPLOCALPORT
environment variable.