linuxbashvariablesscriptingvariable-expansion

Expand a bash variable, but not the variables it contains


I need to be able to expand a variable into exactly what is it declaration string is rather than bash expanding other variables in its declaration like it normally does.

variable=word
var="this variable contains a $variable"

echo ????

I need a command that results in: "this variable contains a $variable" ...rather than: "this variable contains a word"

The variable declaration must remain the same because it needs to expand normally in all other situations. So I can't use single quotes in the declaration.


Solution

  • What you're asking for is effectively impossible, because inside double quotes, $variable is substituted by the contents of that variable at the time of definition.
    At the time you're printing it, the expansion has long happened (as can be seen using set).

    If you cannot change the definition at all, the only thing you can try is to revert the substitution:

    echo ${var/"$variable"/'$variable'}
    

    This will replace every occurrence of what $variable currently holds by the string literal $variable, so in your example it would replace all occurrences of word with $variable, regardless of whether it was defined that way or not.
    Also, as Jasper correctly pointed out, this relies on the fact that $variable does not change between the definition and the printing of $var.

    Now, if you actually can change the definition, but only want the expansion to still happen, there are a number of options:

    1. Use single quotes in the definition, and eval when printing.

      var='this variable contains a $variable'
      # later
      eval "echo "'"'"$var"'"'    # -> this variable contains a word
      echo $var                   # -> this variable contains a $variable
      

      Admittedly, this is rather cumbersome.
      Also note that variable expansion happens at the time of printing rather than at the time of definition (actually, just at whatever time eval run).

    2. Use two different variables.

      Instead of writing eval "echo "'"'"$var"'"' every time, you could just store its result in a variable and use that, right?

      var='this variable contains a $variable'
      varX="$(eval "echo "'"'"$var"'"')"
      # later
      echo $varX      # -> this variable contains a word
      echo $var       # -> this variable contains a $variable
      

      Or, of course just copy the definition of $var and replace ' by "... but that's boring. :P

      Here the expansion happens at the time of defining $varX.

    3. Use an array instead of two different variables.

      This is really interesting, because for arrays $var is equal to ${var[0]}, so you can do

      tmp='this variable contains a $variable'
      var=("$(eval "echo "'"'"$tmp"'"')" "$tmp")
      # later
      echo $var           # -> this variable contains a word
      echo ${var[1]}      # -> this variable contains a $variable
      

      This is probably as close as it gets to what you want, but you will still have to change the definition.