0

I have an environment setup script where I want to alert the user as to what commands I'm executing (i.e. what is being installed). Using set -x makes the output too cluttered, so I have a function successfully that's called on commands that are important:

#!/bin/bash
trap "exit 1" TERM
export TOP_PID=$$

real_exit() {
  echo -e "Goodbye :'("
  kill -s TERM $TOP_PID
}

successfully() {
  echo -e "$*"
  $* || (echo -e "\nFailed. Check output and then retry, please." 1>&2 && real_exit)
}

For example, I can call successfully brew update in my script, and if it fails, the user knows what command it fails on and the script stops.

However, my script is failing when I try to install Ruby/RVM:

successfully "curl -L https://get.rvm.io | bash -s stable --ruby"

The curl command works fine when I call it from the command line, yet it fails when the script calls it with the error:

curl -L https://get.rvm.io | bash -s stable --ruby
curl: option --ruby: is unknown
curl: try 'curl --help' or 'curl --manual' for more information
rainbowsprinkles
  • 257
  • 5
  • 11
  • 1
    [I'm trying to put my command in a variable, but the complex cases always fail](http://mywiki.wooledge.org/BashFAQ/050) – tripleee Sep 10 '13 at 05:15
  • Proper quoting is `"$@"` not `$*`, but you still cannot make it work with pipes. – tripleee Sep 10 '13 at 05:16
  • @tripleee Thanks for the links! I'm new to bash scripting, so I had no idea what to look for. Could you explain why proper quoting would be "$@"? – rainbowsprinkles Sep 10 '13 at 05:31
  • Briefly,`$*` was the original construct, which proved to be buggy when the argument list contained arguments containing whitespace; so a correct version was introduced instead. If you don't want to think about it too much, just learn to always use `"$@"` in double quotes. – tripleee Sep 10 '13 at 07:42

1 Answers1

1

You would need to use eval, but it's not recommended.

successfully() {
    echo -e "$1"
    eval "$1" || (echo -e "\nFailed. Check output and then retry, please." 1>&2 && real_exit)
}

Also send SIGHUP instead of SIGTERM:

  kill -s SIGHUP "$TOP_PID"

You could use a safe variation like this as well:

successfully() {
    local __A=() __I
    for (( __I = 1; __I <= $#; ++__I )); do
        if [[ "${!__I}" == '|' ]]; then
            __A+=('|')
        else
            __A+=("\"\$$__I\"")
        fi
    done
    echo "$@"
    eval "${__A[@]}" || (echo -e "\nFailed. Check output and then retry, please." 1>&2 && real_exit)
}

Example:

successfully curl -L https://get.rvm.io \| bash -s stable --ruby  ## You just have to quote pipe.
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • 1
    Awesome, thank you! That works perfectly for me. I will have to dig in to how it works tonight. And about SIGTERM - I'm using the function from a [question about terminating a script](http://stackoverflow.com/a/9894126/1282635); it doesn't terminate the terminal (which I think is implied by SIGHUP). – rainbowsprinkles Sep 10 '13 at 05:42