My BASH Template for Executable Scripts
Phil Hadviger
Principal Site Reliability Engineer @ GLG#
The TemplateSince I can never truly remember all there is to bash, i've made a template I re-use for a lot of my bash scripts, to remind myself of useful starting points. I'll post the whole template first, and then I'll explain it in smaller sections below.
readlink
#
The template uses readlink
as a way to find a full paths of files and directories. I've found readlink
to be the most reliable method, but it isn't created equally on Linux and OS-X (and some other systems). So in parts of the template you'll see me use it like this: "${readlink}" -e -- /tmp/bla
. I need to use the GNU compatible version of readlink
and so I create a variable for it, and execute that one instead a hardcoded executable. The readlink -e
part detects if the current version of readlink
supports the -e
flag, and if it doesn't it tries to use greadlink
instead. Not 100% failsafe, but works well enough so far.
#
The VariablesThese are fairly self explanatory, and are used in other parts of the template. The _PWD
that's used here, is purely present cause I could not get this template to properly color code in the Blog. I would other omit it.
#
Bash Strict ModeAaron Maxwell wrote about this in detail on his site, so head on over there if you are interested on what is all about.
I use to to more reliably process input, and fail on unset variables and errors.
#
Exit TrapNot all of my scripts use a trap this elaborate, but like I mentioned the template is just a reminder of how to do things.
Traps are just an amazing way to tie up a script, provide a little more insight into how the script ended, and also clean up temporary resources that might have been created by a script.
Greg "GreyCat"'s Wiki has an in-depth overview of traps and signals.
In some cases, having a variety of different ways of handling an exit can be very nice. One thing to be aware of about signal handlers like SIGINT, (rather than using the EXIT trap), is that the process should kill itself with SIGINT rather than simply exiting, to avoid causing problems for its caller.
Also, traps need to be defined as high as possible in the scripts to actually handle all the signals and errors properly.
#
LoggingOne of the most important setups I use. This takes both stderr
and stdout
and prints the to the screen and a log file, for all commands in this script. So no need to run command > file-name 2>&1
because the output will already be in a log file of your choice.
Note: Some scripts can make great benefit of keeping stderr separate from stdout, especially when the output of the command is used in a pipe. For those scripts, I would not recommend sending both streams to the tee command, and instead only send stdout
.
#
RestrictionsDetecting if you the user is executing the script from the correct directory can be critical in some cases. The aim of this block is exactly that. In other cases though, it might just be that the variables SCRIPT_DIR
and CURRENT_DIR
are more important, and those can be used to make the script more directory agnostic. Really depends on the needs.
Preventing a script from being executed as root, or making sure it's a certain user, can be done using the id
command. In this case I'm looking for a number, since root is always 0, but id
in combination with the --name (-n)
flag, can also be used to display the name value of the user, group, etc. I have to use -u
vs --user
in order to make the block more cross-compatible with Macs.
#
OS DetectionA block I keep because my dotfiles aims to run both on Macs and Linux. Outside of that, nothing very exciting. -s
is used here over --kernel-name
again for Mac compatibility.
#
Random Things About The Template#
stderr redirection prefixYou see a lot of >&2
scattered all over this script at the beginning of the line. All that does is send the output to stderr. Now in the case of the template as it shows above, that doesn't do much, since all the output is sent to the tee
command as stdout, but if that weren't the case, those messages are intentionally sent to stderr, so they would be excluded from grep
and such if there was a need.