I have a bit of an obsession with bash scripting, to the extent that I've probably read thru the entire man page several times over by now. I've been bash scripting since I was a kid but I really started getting into it in my professional careers as well as at home in my lab. Over time I've found various techniques and tactics that can be useful when scripting away so figured I'd share some of them here for documentation sake.

printf

This first ones are pretty simple: printing text. I like to use printf over echo due to the ability to specify a format and have just a little more power over what you're writing to the terminal.

function :printf () {
  builtin printf -- "$1" "${@:2}"
}
  • The -- is used to ensure the format isn't accidentally interpreted as printf options
  • builtin isn't required if the function name is something other than printf otherwise recursion would (likely?) ensue.

Usage:

:printf '%s, %s!\n' 'Hello' 'World'
# Hello, World!
๐Ÿ’ก
I like to prefix my bash functions with a `:` (colon) for namespacing and to avoid collision with other functions/commands.

To build on this printf helper function I like to include a couple additional functions:

vprintf

function :vprintf () {
  printf -v "$1" -- "$2" "${@:3}"
}

# Usage:
:vprintf SAMPLE_VAR '%s - %s\n' 'abc' '123'
declare -p SAMPLE_VAR
# declare -- SAMPLE_VAR="abc - 123"

Assign a formatted value to a variable instead of printing to the terminal

printfln

function :printfln () {
  :printf "${1}\n" "${@:2}"
}

Shortcut for printing a formatted line

vprintfln

function :vprintfln () {
  :vprintf "$1" "${2}\n" "${@:3}"
}

Shortcut for assigning a formatted variable value including a new line

print

function :print () {
  :printf '%s' "$@"
}

Shortcut for printing to the terminal (without a new line)

vprint

function :vprint () {
  :vprintf "$1" '%s' "${@:2}"
}

Shortcut for assigning a variable value (without a new line)

println

function :println () {
  :printfln '%s' "$@"
}

Shortcut for printing a line to the terminal

vprintln

function :vprintln () {
  :vprintfln "$1" '%s' "${@:2}"
}

Shortchut for assigning a variable value including new line


define

This next one is a slightly more complex way to assign a variable value using the read builtin and shell redirections, such as a heredoc, and while preserving whitespace. This can be very useful, especially if you want to use a subshell redirection for some fancy programmatic value building: :define VAR_NAME <(some_script).

function :define () {
  IFS=$'\n' read -r -d '' "$@" || true
}
  • IFS=$'\n' - Sets the Internal Field Separator to a newline character (used for word splitting)
  • -r - "Do not allow backslashes to escape any characters" (See: help read)
  • -d '' - "Continue until the first character of DELIM is read, rather than newline" (See: help read) This ensures read consumes all of stdin
  • || true - Ensures read doesn't hang since it will exit with a non-zero return code when the end of the file is encountered

Usage:

:define SAMPLE_VAR <<'EOD'
This is a sample document.
  Leading whitespace is preserved.
EOD

declare -p SAMPLE_VAR
# declare -- SAMPLE_VAR="This is a sample document.
#   Leading whitespace is preserved."
Here document - Wikipedia

"a here document is a file literal or input stream literal: it is a section of a source code file that is treated as if it were a separate file"


readline

This last one is just a simple but useful helper for while loops and reading line by line into a variable while preserving whitespace.

function :readline () {
  IFS= read -r "$@"
}
  • IFS= - Sets the Internal Field Separator blank so the whole line is captured
  • -r - "Do not allow backslashes to escape any characters" (See: help read)

Note: Even though only one variable name works here, we use "$@" instead of just "$1" incase any additional read options are desired.

Usage:

while :readline line
do
  declare -p line
done <<'EOD'
Hello,
  World!
EOD
# declare -- line="Hello,"
# declare -- line="  World!"

Well, hopefully there's something of value here for someone. I know these are pretty simple and straightforward if you're already familiar with bash scripting, but I like to establish a little bit of a bootstrap like this before writing out my scripts. Let me know in the comments if you have any questions or know of anything I missed or got wrong, or any other neat techniques for bashing! Happy scripting