6

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?

11

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 )
  • 1
    (Unless control+c is involved in which case what happens to the remainder of the commands depends on the shell...) – thrig Jun 5 '17 at 19:58
  • 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. – Ian Jun 6 '17 at 10:22
7

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)
  • 1
    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. – denis.peplin Jul 24 '18 at 8:02
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.

1
mycmd.sh && { rm -r temp_files;  true; } || { rm -r temp_files; false; }
0

I use the && and || operators exclusively now instead of if;then. But sometimes it's just a lot clearer to go back to `if;then'. This is one of those times since you require a commandline solution.

I would rewrite it like this...

$ if mycmd.sh; then { success; actions; } else { fail; actions; } fi;  rm -rf temp_files/
  • mycmd.sh runs and if uses its result
  • The curly braces ensure that the list of multiple commands inside are handled as one block and they enhance clarity. The spaces around the braces and semicolon after each command within, including the last, are mandatory.
  • Since the rm occurs after the fi, it will execute unconditionally.
  • 2
    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. – user218374 Jun 5 '17 at 22:02
  • Not at all. The if is using it immediately. if doesn't actually care what mycmd.sh does. It only cares about whether it returns true(0) or false(>0). You could save $? to a variable and use it later, as the OP indicates, but then you'd have to test it using the seldom-used (and thus error prone) numeric syntax. Sometimes that's necessary, but it's best to avoid it if you can. The OP asked for "a more idiomatic way." This is one. – DocSalvager Jun 6 '17 at 0:52
  • 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 Jun 6 '17 at 10:12
  • 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 Jun 6 '17 at 17:20
  • 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. – DocSalvager Jun 6 '17 at 17:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.