I've been wrestling with bash variable substitution for a while now and can't figure this out...

I have a variable with a command template:

CMD_TMPL='sudo -u ${USER_NAME} ${USER_HOME}/script.sh'

The variables USER_NAME and USER_HOME are figured out later in the script, not yet known at the time CMD_TMPL is defined. Therefore the command is in single-quotes and the are not yet substituted.

Then the script figures out USER_NAME=test and USER_HOME=/home/test and I want to do something that will lead to ${CMD} containing:

sudo -u test /home/test/script.sh

Further down in the script I will use that command in a pipe like:

${CMD} | output-processing.sh

How do I achieve the expansion from variable names in ${CMD_TMPL} to variable values in ${CMD}? I tried all sorts of echo's and eval's but can't figure it out.


  • You say you've tried eval already. Did eval "$CMD_TMPL" not work? What went wrong with it? – Michael Homer Aug 11 '14 at 6:22
  • 1
    Why does CMD_TMPL need to be declared prior to $USER_NAME and $USER_HOME? – Warwick Aug 11 '14 at 6:23
  • @Warwick - the template is in a header of the file together with other settings. I don't want a hardcoded setting way down buried in the script where it is actually used. – MLu Aug 11 '14 at 6:29
  • @MichaelHomer - eval doesn't only substitute the variables but also immediately executes the command. That's not what I want - I want the expanded vars assigned to a new variable first. That's a requirement for logging and auditing... I'm sure it should be doable...? – MLu Aug 11 '14 at 6:31

There's a special type of variable that is used to store code, that's functions:

cmd_tmpl() { sudo -u "$USER_NAME" "$USER_HOME/script.sh" "$@"; }
cmd() { cmd_tmpl other args "$@"; }
cmd | output_processing...

The other approaches are to consider the variables as command lines (shell code) and interpret that code in the end using eval:

cmd_tmpl='sudo -u "$USER_NAME" "$USER_HOME/script.sh"'
cmd=$cmd_tmpl' other args as shell code'
eval "$cmd" | output_processing...

Using $cmd | output_processing is wrong. $cmd is a scalar variables and you're using the split+glob operator to obtain the list of arguments to a simple command, you're not evaluating the command line stored in it. So that only works in a constrained set of conditions.

You can store arguments to a simple command in an array:

cmd=(sudo -u "$USER_NAME" "$USER_HOME/script.sh" ...)
"${cmd[@]}" | post_processing

but that won't help with the deferred expansion of your variables.

You could defer the expansion though with things like:

cmd_tmpl='sudo -u "$USER_NAME" "$USER_HOME/script.sh"'
eval "cmd=($cmd_tmpl)" # now expanded
cmd+=(other args)
"${cmd[@]}" | post-processing

(note that it's good practice to reserve all-uppercase variable names to variables exported to the environment).

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.