0

I am writing a basic script that has something like this:

LOG_COMMAND=">/tmp/something.log 2>&1"
if [ "${VERBOSE}" == "y" ]; then
  LOG_COMMAND="2>&1 | tee /tmp/something.log"
fi

The problem is that when I use the LOG_COMMAND variable in my script it gets wrapped with single quotes. For example:

set -v
docker build -t test:test . ${LOG_COMMAND}
set +v

I get the following output:

$ /bin/bash testing.sh -v
+ docker build -t test:test . '2>&1' '|' tee /tmp/something.log
"docker build" requires exactly 1 argument.
See 'docker build --help'.

How can I prevent the single quotes from being included?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
el n00b
  • 1,957
  • 7
  • 37
  • 64
  • Possible duplicate of [eval command in Bash and its typical uses](https://stackoverflow.com/questions/11065077/eval-command-in-bash-and-its-typical-uses) – jeremysprofile Aug 17 '18 at 18:51
  • @jeremysprofile that works. If you want you can mark it as a duplicate, but this is a pretty specific example. – el n00b Aug 17 '18 at 18:54
  • 1
    So, I don't love the way SO does duplications. Basically, they tell you that duplicates aren't a mark of a poor question, but they can definitely feel that way, like the other person is saying you didn't do enough research. Duplicates exist to consolidate knowledge on a topic in one place, allowing the best answers to rise to the top with a bunch of people voting in 1 area, instead of having a bunch of mediocre answers all over the place. The question marked provides enough detail to functionally answer your question, thus your question should not have its own individual answers. – jeremysprofile Aug 17 '18 at 19:03
  • We have [Why does shell ignore quotes in arguments passed to it through variables?](https://stackoverflow.com/questions/12136948/why-does-shell-ignore-quotes-in-arguments-passed-to-it-through-variables) for this problem relating to quotes, but we really need one for shell syntax in general. – that other guy Aug 17 '18 at 20:08
  • 1
    I disagree that this is a good use of `eval`. As described in [BashFAQ #48](http://mywiki.wooledge.org/BashFAQ/048), `eval` should only be used when there exists no other alternative -- using it for redirections would mean that code embedded in your logfile names would be executed, which is a problem if you're letting a less-privileged user run and control output for a more-privileged script. There are plenty of other alternatives for conditional logging. As such, @jeremysprofile, I question the applicability of that particular duplicate. – Charles Duffy Aug 17 '18 at 20:44
  • 1
    BTW, [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050) (*I'm trying to put a command in a variable, but the complex cases always fail!*) is quite pertinent to this question. – Charles Duffy Aug 17 '18 at 20:47
  • @CharlesDuffy, fair. I was not thinking enough about ways to avoid eval. Both answers provided do a better job, I agree. – jeremysprofile Aug 17 '18 at 20:54

2 Answers2

3

The simple and robust solution is to put code you want to reuse in a function, rather than in a variable:

log() {
    if [ "$VERBOSE" = "y" ]
    then
        "$@" 2>&1 | tee -a /tmp/something.log
    else
        "$@" >> /tmp/something.log 2>&1
    fi
}

log docker build -t test:test .

The single quotes you see are not being added in any way, they're just bash's notation to show you that the value is a literal string and not part of the command's syntax. "Removing" them wouldn't change the fact that they are just uninterpreted strings any more than whoami() { echo root; } makes you the super user.

If it helps put things into context, Java has the same "problem" with the same preferred solution:

// Works
System.out.println("hello world".replaceAll("hello", "goodbye"));

// Fails
String replacer = ".replaceAll(\"hello\", \"goodbye\")";
System.out.println("hello world" + replacer);
that other guy
  • 116,971
  • 11
  • 170
  • 194
2

Variables store data, not code; unless you're going to do something evil, you can't use a variable for this purpose. Instead, just perform the indirection ahead-of-time:

case $VERBOSE in
  y) exec 2>&1 > >(tee /tmp/something.log)
  *) exec >/tmp/something.log 2>&1
esac

...will redirect stdout and stderr for all code below them. If you only want to make some code subject to that, set up a different pair of conditionally-redirected file descriptors:

case $VERBOSE in
  y) exec 3> >(tee /tmp/something.log)
  *) exec 3>/tmp/something.log
esac

...and redirect the commands that you want to be subject to those logging rules to that FD:

something_that_should_be_logged >&3 2>&3
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441