Getting "ensure" / "finally" functionality in a shell command (not script)?
Solution 1
Newlines in a script are almost always equivalent to semicolons:
mycmd.sh; ret=$?; rm -rf temp_files; exit $ret
In response to the edit:
Alternatively, you could also use a trap
and a subshell:
( trap 'rm -rf temp_files' EXIT; mycmd.sh )
Solution 2
If you're looking for a copy of some languages' try { } finally { }
,
there is another way: using the trap
builtin in bash
and other POSIXy shells (see help trap
).
#!/bin/bash
# exit with this by default, if it is not set later
exit_code=0
# the cleanup function will be the exit point
cleanup () {
# ignore stderr from rm incase the hook is called twice
rm -rf "temp_files/" &> /dev/null
# exit(code)
exit $exit_code
}
# register the cleanup function for all these signal types (see link below)
trap cleanup EXIT ERR INT TERM
# run your other script
mycmd.sh
# set the exit_code with the real result, used when cleanup is called
exit_code=$?
Read about the trap command's arguments.
Note that cleanup
is called:
- if this script is sent SIGINT or SIGTERM or if CTRL-C is pressed (SIGINT)
- if this script exits normally with 0
- if mycmd.sh exits with nonzero status (maybe not what you want -- remove
ERR
from trap's arguments to disable)
Solution 3
In zsh
:
{mycmd.sh} always {rm -rf temp_files}
The always part will be executed even in case of an error like a glob with no match or runtime syntax error that would exit the script.
Solution 4
mycmd.sh && { rm -r temp_files; true; } || { rm -r temp_files; false; }
Related videos on Youtube
Ian
Updated on September 18, 2022Comments
-
Ian almost 2 years
I need to know whether a command has succeeded or failed, and unconditionally run some cleanup afterward.
Neither of the normal options for executing sequential commands seem to be applicable here:
$ mycmd.sh && rm -rf temp_files/ # correct exit status, cleanup fails if mycmd fails $ mycmd.sh ; rm -rf temp_files/ # incorrect exit status, always cleans up $ mycmd.sh || rm -rf temp_files/ # correct exit status, cleanup fails if mycmd succeeds
If I was going to do it in a shell script, I'd do something like this:
#!/usr/bin/env bash mycmd.sh RET=$? rm -rf temp_files exit $RET
Is there a more idiomatic way to accomplish that on the command line than semicolon-chaining all those commands together?
-
thrig about 7 years(Unless control+c is involved in which case what happens to the remainder of the commands depends on the shell...)
-
Admin about 7 yearsBut the exit status of the mycmd.sh script is lost. The OP wants an unconditional cleanup of temp_files/ directory plus the exit status of the mycmd.sh as the final exit status.
-
Ian about 7 yearsCan you explain why running
echo $?
after this command will give the return value ofmycmd.sh
and not ofrm -rf temp_files/
? -
Ian about 7 yearsThanks for noticing my edit -- I think we were both typing at the same time.
trap
is exactly the sort of thing I was looking for: no temporary variables, no duplicate commands, and fewer characters than my original "script" idea. -
DocSalvager about 7 yearsHow do you know which command the return code (
$?
) is for?$?
is very fleeting. Every single action resets the return code to a 1-byte integer value that is really the error code result. Most of the time, we don't use it, but it has been reset anyhow. To add to the confusion, some commands are not very scrupulous about what they ultimately return. The default behavior is to return the result code of the last operation within the command. -
DocSalvager about 7 yearsEvery action changes the value of
$?
... includingecho
itself which always returns true(0). So inecho $?; echo $?
, the second echo will always print 0. -
Ian about 7 yearsThis fails to accomplish the desired behavior in the OP question.
-
denis.peplin almost 6 yearsNote that
cleanup
will run twice onCTRL-C
. It can be avoided by removingEXIT
from signals and callingcleanup
explicitly aftermycmd.sh
.