263

I'm trying to interpolate variables inside of a bash heredoc:

var=$1
sudo tee "/path/to/outfile" > /dev/null << "EOF"
Some text that contains my $var
EOF

This isn't working as I'd expect ($var is treated literally, not expanded).

I need to use sudo tee because creating the file requires sudo. Doing something like:

sudo cat > /path/to/outfile <<EOT
my text...
EOT

Doesn't work, because >outfile opens the file in the current shell, which is not using sudo.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Jon
  • 3,280
  • 2
  • 15
  • 16
  • 13
    This is an understandable confusion! As noted below, quoting any part of the delimiter turns off expansion in the heredoc (as if it were in `''`), but *not* quoting the delimiter turns expansion on (as if it were in `""`). *However,* your intuition is correct in Perl, where a heredoc with single-quoted identifier behaves as if it were in single quotes, one with a double-quoted identifier as if in double quotes, and one with back-ticked identifier as if in backticks! See: [perlop: < – Nils von Barth Nov 12 '14 at 05:14

3 Answers3

318

In answer to your first question, there's no parameter substitution because you've put the delimiter in quotes - the bash manual says:

The format of here-documents is:

      <<[-]word
              here-document
      delimiter

No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. [...]

If you change your first example to use <<EOF instead of << "EOF" you'll find that it works.

In your second example, the shell invokes sudo only with the parameter cat, and the redirection applies to the output of sudo cat as the original user. It'll work if you try:

sudo sh -c "cat > /path/to/outfile" <<EOT
my text...
EOT
Th. Ma.
  • 9,432
  • 5
  • 31
  • 46
Mark Longair
  • 446,582
  • 72
  • 411
  • 327
128

Don't use quotes with <<EOF:

var=$1
sudo tee "/path/to/outfile" > /dev/null <<EOF
Some text that contains my $var
EOF

Variable expansion is the default behavior inside of here-docs. You disable that behavior by quoting the label (with single or double quotes).

mob
  • 117,087
  • 18
  • 149
  • 283
78

As a late corolloary to the earlier answers here, you probably end up in situations where you want some but not all variables to be interpolated. You can solve that by using backslashes to escape dollar signs and backticks; or you can put the static text in a variable.

Name='Rich Ba$tard'
dough='$$$dollars$$$'
cat <<____HERE
$Name, you can win a lot of $dough this week!
Notice that \`backticks' need escaping if you want
literal text, not `pwd`, just like in variables like
\$HOME (current value: $HOME)
____HERE

Demo: https://ideone.com/rMF2XA

Note that any of the quoting mechanisms -- \____HERE or "____HERE" or '____HERE' -- will disable all variable interpolation, and turn the here-document into a piece of literal text.

A common task is to combine local variables with script which should be evaluated by a different shell, programming language, or remote host.

local=$(uname)
ssh -t remote <<:
    echo "$local is the value from the host which ran the ssh command"
    # Prevent here doc from expanding locally; remote won't see backslash
    remote=\$(uname)
    # Same here
    echo "\$remote is the value from the host we ssh:ed to"
:
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 3
    Not sure why this was down-voted, but it adds a valid note which is _not_ covered in the now higher voted answer. – Inian Jan 30 '19 at 07:16