Getting "ensure" / "finally" functionality in a shell command (not script)?

6,738

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; }
Share:
6,738

Related videos on Youtube

Ian
Author by

Ian

Updated on September 18, 2022

Comments

  • Ian
    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
    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
    Admin about 7 years
    But 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
    Ian about 7 years
    Can you explain why running echo $? after this command will give the return value of mycmd.sh and not of rm -rf temp_files/?
  • Ian
    Ian about 7 years
    Thanks 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
    DocSalvager about 7 years
    How 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
    DocSalvager about 7 years
    Every action changes the value of $?... including echo itself which always returns true(0). So in echo $?; echo $?, the second echo will always print 0.
  • Ian
    Ian about 7 years
    This fails to accomplish the desired behavior in the OP question.
  • denis.peplin
    denis.peplin almost 6 years
    Note that cleanup will run twice on CTRL-C. It can be avoided by removing EXIT from signals and calling cleanup explicitly after mycmd.sh.