Skip to content

Instantly share code, notes, and snippets.

@cedws
Last active July 5, 2024 13:31
Show Gist options
  • Save cedws/ab8da14beb3aad4521ba6b612956a83e to your computer and use it in GitHub Desktop.
Save cedws/ab8da14beb3aad4521ba6b612956a83e to your computer and use it in GitHub Desktop.
Go-like defer 'keyword' for shell
#!/bin/sh
DEFER=
defer() {
DEFER="$*; ${DEFER}"
trap "{ $DEFER }" EXIT
}
@zapashcanon
Copy link

Yep', I confirm it's fixed. Thanks. :)

@charles-dyfis-net
Copy link

charles-dyfis-net commented Jun 21, 2023

This is very prone to shell injection vulnerabilities as it's currently written.

With bash 5.0, you can mitigate that using ${*@Q} in place of $*.

With earlier releases, you can use printf -v cmd_q '%q ' "$@" to generate an eval-safe escaping of an arbitrary command (stored in the variable $cmd_q).

Also, consider staying out of the all-caps namespace; as POSIX advises at https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html, all-caps names are used for variables that reflect or modify behavior of the shell and other POSIX-specified tools. (Because regular shell variables and environment variables share a single namespace -- setting a shell variable will overwrite any like-named environment variable -- the same conventions apply to both).

@denishowe
Copy link

denishowe commented Jun 21, 2023

The correct noun form of the adjective "idempotent" is "idempotence", not *idempotency*.
An idempotent operation is one that has the same effect whether you apply it once or multiple times.
Not leaking (which your trap wrapper could help with) is one aspect aspect of this but not the main meaning.

@paride
Copy link

paride commented Jun 21, 2023

This will not handle spaces correctly, try for example

defer echo "a  b" # two spaces between 'a' and 'b'

the output will have one space:

a b

@tkapias
Copy link

tkapias commented Jun 21, 2023

Looking at the ongoing comments on HN and here, I'll go with @charles-dyfis-net's version which handle spaces correctly (bash5.0+ only).

#!/usr/bin/env bash

DEFER=

defer() {
    DEFER="${*@Q}; ${DEFER}"
    trap "{ $DEFER }" EXIT
}

# some tests
TEMP=$(mktemp)
touch "/tmp/a  b"
defer rm -vf "$TEMP" "/tmp/a  b"
ls -lsah "$TEMP"
ls -lsah "/tmp/a  b"

The solutions for shell seemed too complicated or wrong.

@charles-dyfis-net
Copy link

charles-dyfis-net commented Jun 21, 2023

As an addendum (since the suggestions I made above are applicable either to bash 5+ or shells with the ksh93 printf %q extension such as bash or zsh):

For folks who want both safety and compatibility with /bin/sh, I'd suggest encapsulating cleanup code in functions (with no arguments -- getting content from variables only) and then passing the names of those functions to defer. rm_tmp_ab() { rm -vf "$TEMP" "/tmp/a b"; }; defer rm_tmp_ab f/e.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment