Skip to content

Instantly share code, notes, and snippets.

@matti
Last active February 14, 2024 16:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matti/3d2f04b1a9485a3a15d0267a68b341ba to your computer and use it in GitHub Desktop.
Save matti/3d2f04b1a9485a3a15d0267a68b341ba to your computer and use it in GitHub Desktop.

Beautiful Bash

until something is true

if "creation" command fails, sleep 1

while true; do
  kubectl get namespace mynamespace && break
  kubectl create namespace mynamespace || sleep 1
done

until something is not true anymore

while true; do
  kubectl get namespace mynamespace || break
  kubectl delete namespace mynamespace || sleep 1
done

error handler

nicer errors:

ERR /path/to/file:32 command-that-failed-without-output exited with 1
_on_error() {
  trap '' ERR
  line_path=$(caller)
  line=${line_path% *}
  path=${line_path#* }

  echo ""
  echo "ERR $path:$line $BASH_COMMAND exited with $1"
  exit 1
}
trap '_on_error $?' ERR

prefix

(
  exec ping google.com
) 2>&1 | sed -le "s#^#-- pinger -- #;"
-- pinger -- PING google.com (216.58.209.174): 56 data bytes
-- pinger -- 64 bytes from 216.58.209.174: icmp_seq=0 ttl=58 time=22.026 ms
-- pinger -- 64 bytes from 216.58.209.174: icmp_seq=1 ttl=58 time=21.474 ms

prefix and suffix

echo "lol" | sed -e "s/.*/hello & world/"
hello lol world

prefix and strip ansi

(
  colorized-command
) 2>&1 | sed -le 's/\x1b\[[0-9;]*m//g' | sed -le "s#^#-- nocolors -- #;"

_debugger

_debugger() {
  while true; do
    printf "\ndebugger> "; read line
    [ "$line" = "exit" ] && break
    eval "$line" || true
  done
}

echo "setting hello"
hello=world
_debugger
start
debugger> echo $hello
world

supress output

(
  exec command_that_outputs_to_stdout_and_stderr
) >/dev/null 2>&1

_echoerr

_echoerr() {
  2>&1 echo "$@"
}

_echoerr "debug messages etc to stderr"

_forever

_forever() {
  while true; do    
    eval "$@" && break
    sleep 1
  done
}

_forever ssh user@host -- uptime
_echoerr "ssh succeeded!"

_never

_never() {
  while true; do    
    eval "$@" || break
    sleep 1
  done
}

_never ping -c 1 google.com
_echoerr "ping timeout"

default values

echo ${something:-or this}
# or this

something="is now set"
echo ${something:-or this}
# is now set

parameter expansion

string="hello==world"

echo ${string%=*}
# hello=

echo ${string%%=*}
# hello

echo ${string#*=}
# =world

echo ${string##*=}
# world

opts

for opt in $@; do
  case $opt in
    --some-thing=*)
      some_thing=${opt#*=}
    ;;
    --other=*)
      other=${opt#*=}
    ;;
    *)
      echo "unknown opt: $opt"
      exit 1
    ;;
  esac
done

without last arg:

opts="${@:1:${#}-1}"
for opt in $opts; do ...

trap

note, after setting ERR trap, set +e no longer works to prevent trap http://mywiki.wooledge.org/BashFAQ/105

_shutdown() {
  # clear traps when already shutting down
  trap '' TERM INT ERR
  
  # send kill to entire process tree (including self, that's why ingoring above)
  kill 0
  
  # wait for all processes
  wait
  
  # unless called new processes can start after trap exits
  exit 0
}

trap _shutdown TERM INT ERR

gather exit codes of backgrounded processes

You can only wait once, so store status as a separate associative array

declare -A pids
for greeting in hello hi yo; do
  (
    sleep 1
    echo "$greeting"
  ) &
  pids[$greeting]=$!
done

declare -A statuses
for greeting in "${!pids[@]}"; do
  pid=${pids[$greeting]}
  set +e
    wait $pid
    code=$?
  set -e
  if [ "$code" = "0" ]; then
    statuses[$greeting]=ok
  else
    statuses[$greeting]=fail
  fi
done

for greeting in "${!statuses[@]}"; do
  status=${statuses[$greeting]}
  echo "greeting $greeting was $status"
done

entrypoint.sh

#!/usr/bin/env bash

set -eEuo pipefail

_on_error() {
  trap '' ERR
  line_path=$(caller)
  line=${line_path% *}
  path=${line_path#* }

  echo ""
  echo "ERR $path:$line $BASH_COMMAND exited with $1"
  exit 1
}
trap '_on_error $?' ERR

_shutdown() {
  # clear traps when already shutting down
  trap '' TERM INT ERR
  
  # send kill to entire process tree (including self, that's why ingoring above)
  kill 0
  
  # wait for all processes
  wait
  
  # unless called new processes can start after trap exits
  exit 0
}

trap _shutdown TERM INT

echo "starting /some/process and discarding output ..."
(
  exec /some/process
) >/dev/null 2>&1 &

echo "starting /another/process and storing output ..."

(
  exec /another/process
) >/tmp/another.log 2>&1 &

echo "starting /yet/another/process and prefixing output ..."

(
  exec ping google.com
) 2>&1 | sed -le "s#^#pings: #;" &

echo "now let's hang so that the container stays alive"

tail -f /dev/null & wait
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment