Short BASH Snippets

BASH script starter

I put this at the top of all my scripts because most of the time I want scripts to fail on errors, and half the time I want the script to run in the directory it's in.


# exit the script on command errors or unset variables
set -euo pipefail

# readonly script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# cd "${script_dir}"

Get the full path to a file

This is perl wrapped in Bash, but it's cross-platform and works on Mac and Linux. The alternative, readlink -f doesn't work on Mac.

fullpath() {
    local -r full=$(perl -e 'use Cwd "abs_path";print abs_path(shift)' "$1")
    echo "$full"

This snippet prints the command before running it. Stolen from StackOverflow. Great for debugging!

set -x
{ set +x; } 2>/dev/null

Generate and use colored print commands

Running scripts with colored output can make them much friendlier. Consider taking out the newlines if you want nested color prints. I almost never do, so I'm leaving them in...

Define the function factory

make_print_color() {
    color_reset="$(tput sgr0)"
    if [ -t 1 ] ; then
        eval "print_${color_name}() { printf \"${color_code}%s${color_reset}\\n\" \"\$1\"; }"
    else  # Don't print colors on pipes
        eval "print_${color_name}() { printf \"%s\\n\" \"\$1\"; }"

Generate pretty print functions and use them

make_print_color "red" "$(tput setaf 1)"
make_print_color "green" "$(tput setaf 2)"
make_print_color "yellow" "$(tput setaf 3)"
make_print_color "blue" "$(tput setaf 4)"

print_red "Always"
print_green "Seeing"
print_yellow "in"
print_blue "Color!"

# print to stderr:
print_red "Error!" >&2

Simple but effective backup command.

bak() {
    date_string="$(date +'%Y-%m-%d.%H.%M.%S')"
    if [[ -d "$1" ]]; then
        local -r no_slash="${1%/}"
        cp -r "${no_slash}" "${no_slash}.${date_string}.bak"
    elif [[ -f "$1" ]]; then
        cp "$1" "${1}.${date_string}.bak"
        echo "Only files and directories supported"

Run a shell command on file change

I like to use entr for this. Generate some filenames and pipe them to entr. The -c option clears the screen and the -s option means use the shell.

ls log.txt | entr -c -s 'date && tail log.txt'

I use a snippet similar to this when I want to open a browser after I run a blocking command (usually starting a server). I use this particular example to learn Elm. I have something similar to run Jekyll for my blog.

learn_elm() {
    cd ~/Code/Elm || echo "Non-existant dir"
    code .
    if [[ "$(uname)" == "Darwin" ]]; then
    elif [[ "$(uname)" == "Linux" ]]; then
    # Open a subshell in a fork
    (sleep 2 && "${open_command}" "") &
    # Run the blocking command
    elm reactor

Simple Task Runner

For when you want to run some long commands with a shortcut. It does very limited arg parsing.

    cat << EOF
    $0 first|1
    $0 second|2

first() {
    echo "I'm first"

second() {
    echo "I'm second!"

set +u
if [ -z ${1+x} ]; then
set -u

case "$1" in
        echo "Unmatched command: $1"

Tee stderr and stdoutto files

Save both stderr and stdout to a file. Only works in Bash. From StackOverflow

{ { time ./ | tee tmp_import_log.stdout;} 3>&1 1>&2 2>&3- | tee tmp_import_log.stderr;} 3>&1 1>&2 2>&3-

Process each line on a file

From Unix StackExchange. I like to combine it with printing the command used.

while IFS='' read -r line || [ -n "${line}" ]; do
    set -x
    echo "$line"
    { set +x; } 2>/dev/null
done < ./file.txt

You can also pipe lines to the while loop:

pbpaste | while IFS='' read -r line || [ -n "${line}" ]; do
    echo "line: $line"

Iterating inline arrays in Zsh

Not a Bash snippet, but useful nonetheless :) . From SuperUser

for d ( {
    dig +short +noshort "$d"

Diff everything in a directory!

Consider doing a git clean before this:

git clean -xd --dry-run
git clean -xd --force
diff -qr -x '.git' folder1/ folder2/

Cross-platform colored diff

A colleague got this from somewhere on StackOverflow:

function vdiff() {
    # colored diff
    diff $@ | sed 's/^-\([^-]*\)/\x1b[31;1m-\1/;s/^+\([^+]*\)/\x1b[32;1m+\1/;s/^@/\x1b[36;1m@/;s/$/\x1b[0m/'

Customize dig

Unfortunately, there's no way to use multiple name servers

dig +noall +answer +question +identify -q -t ns -q -t a

Search and replace across files

Most people use sed for this, but sed differs between MacOS and Linux. Taken from StackOverflow:

perl -pi -w -e 's/search/replace/g;' *.php
  • -e means execute the following line of code.
  • -i means edit in-place
  • -w write warnings
  • -p loop through lins and print

See Perl 101 - Command-line Switches for other useful Perl switches.

This can be combined with find to run recursively:

find . -name '*.py' -print0 | xargs -0 perl -pi -w -e 's/"2022-04-01"/"2022-04-01-preview"/g;'

It's also possible to ignore files (NOTE: this works on MacOS)

find . -type f -not -path '*/\.git\/*' -not -path '\./' -print0 \
    | xargs -0 perl -pi -w -e 's/example-python-cli/new-project-name/g;'

Clean unwanted Homebrew formulas

Show dependency graph (optional)

brew deps --installed --graph

Show formulas that nothing depends on (and how many dependencies they have) -

brew leaves | xargs brew deps --formula --for-each | sed "s/^.*:/$(tput setaf 4)&$(tput sgr0)/"

Then uninstall something:

brew uninstall [thing]

brew will refuse to uninstall the formula if another formula depends on it.

Run to uninstall dependencies that are no longer needed.

brew autoremove

List images in the terminal

Expecially useful for getting the right images in blog Markdown image links. imgcat comes from these iTerm2 docs.

imgcat -H 400px -r -p index.assets/*

Curl tips

Mastering curl: interactive text guide is an EXCELLENT blog post, but my current favorite tip is how to send an HTTP request to another IP (the below example also only shows the headers).

curl \
    --head \
    --request GET \
    --connect-to \