9

I frequently run a simple bash command:

rpm -Uvh --define "_transaction_color 3" myPackage.rpm

which works properly.

But now I'm trying to script it into a bash file, and make it more flexible:

#!/bin/bash
INSTALL_CMD=rpm
INSTALL_OPT="-Uvh --define '_transaction_color 3'"

${INSTALL_CMD} ${INSTALL_OPT} myPackage.rpm

However, this keeps generating the error:

error: Macro % has illegal name (%define)

The error is coming from how --define and the quoted _transaction_color is handled.
I've tried a variety of escaping, different phrasing, even making INSTALL_OPT an array, handled with ${INSTALL_OPT[@]}.

So far, my attempts have not worked.
Clearly, what I want is extremely simple. I'm just not sure how to accomplish it.

How can I get bash to handle my --define argument properly?

abelenky
  • 63,815
  • 23
  • 109
  • 159
  • You have tried `INSTALL_OPT='-Uvh --define _transaction_color 3'`, right? – sampson-chen Nov 13 '12 at 17:27
  • @sampson-chen: Tried that. "error: Macro %_transaction_color has empty body". – abelenky Nov 13 '12 at 17:36
  • 1
    This is because bash reads your arguments as `-Uvh` and `--define` and `'_transaction_color` and `3'`. You **must** use an array in this case (as shown below in Barmar's answer): `INSTALL_OPT=(-Uvh --define '_transaction_color 3')`. Then `${INSTALL_CMD} "${INSTALL_OPT[$@]}" myPackage.rpm` works (with double quotes, as he mentionned). – gniourf_gniourf Nov 13 '12 at 18:00

2 Answers2

14

The problem is that quotes are not processed after variable substitution. So it looks like you're trying to define a macro named '_transaction_color.

Try using an array:

INSTALL_OPT=(-Uvh --define '_transaction_color 3')

then:

"$INSTALL_CMD" "${INSTALL_OPT[@]}" myPackage.rpm

It's important to put ${INSTALL_OPT[@]} inside double quotes to get the requoting.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • I will only `+1` if you edit your post and remove the `evil` thing. It's terrible! Just leave the one with the array, as it is the only good practice! – gniourf_gniourf Nov 13 '12 at 18:02
  • Eval is evil if you're using untrusted user input. If you're using it with your own data, it's not so bad. – Barmar Nov 13 '12 at 18:09
  • 1
    +1 for the array answer -- it's the only way to properly handle multiple words that may contain whitespace. – glenn jackman Nov 13 '12 at 18:11
  • Yes, or `echo "$CMD $OPT blahblah" | /bin/bash`... Is this less *evil* than `eval` @gniourf_gniourf? – F. Hauri - Give Up GitHub Nov 13 '12 at 18:26
  • @F.Hauri It's more evil if you think running unnecessary processes is bad. – Barmar Nov 13 '12 at 18:32
  • @Barmar How could you think: *unnecessary*? While *evil* mean *dangerous*, the `eval` command put everything to the current environment, where the fork (`|sh`) ensure to leave you current enviroment unmodified. So if there is a difference, sometime the fork could be necessary (depending on *what you do*)... But at all, I think that *fork to sh* is at same level of security risk than *eval*. – F. Hauri - Give Up GitHub Nov 13 '12 at 23:13
  • @Barmar, is surrounding `${INSTALL_OPT[@]}` with double-quotes enable requoting or does it prevent the variable substitution from word-splitting? Honest question, not sure if last sentence of [Word Splitting](https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html) is applicable. – nsg Jul 03 '15 at 10:01
  • 1
    @Nate It performs re-quoting. This is explained in [Arrays](https://www.gnu.org/software/bash/manual/html_node/Arrays.html#Arrays): If the word is double-quoted, ... `${name[@]}` expands each element of name to a separate word. – Barmar Jul 03 '15 at 16:22
-1

It might be a bash issue with word splitting on space:

Try:

#!/bin/bash

IFS=$'\n'

INSTALL_CMD=rpm
INSTALL_OPT='-Uvh'
INSTALL_OPT_DEFINE='--define _transaction_color 3'

${INSTALL_CMD} ${INSTALL_OPT} ${INSTALL_OPT_DEFINE} myPackage.rpm
sampson-chen
  • 45,805
  • 12
  • 84
  • 81
  • I suspect `--define` requires the macro name and value to be in a single argument, so this won't work. – Barmar Nov 13 '12 at 17:35
  • @Barmar Good point, I wasn't too sure about that part; updated my answer. – sampson-chen Nov 13 '12 at 17:37
  • @sampson-chen: Tried setting IFS and breaking up the options as you describe. Result: "-Uvh: unknown option". (I don't understand why... but thats the error I got) – abelenky Nov 13 '12 at 17:40
  • @abelenky: Try removing the quotes around `INSTALL_OPT='-Uvh'` (or using double-quotes). Also, I used backticks instead of single-quotes in my original answer by mistake, in case you copy and pasted anything. – sampson-chen Nov 13 '12 at 17:44
  • I saw the back-ticks, and assumed you meant regular-ticks (but I tried it both ways to be sure). It looks like @Barmar's answer works; thanks for the effort. – abelenky Nov 13 '12 at 17:46