549

I have this multi-line string (quotes included):

abc'asdf"
$(dont-execute-this)
foo"bar"''

How would I assign it to a variable using a heredoc in Bash?

I need to preserve newlines.

I don't want to escape the characters in the string, that would be annoying...

4
  • @JohnM - I have just tried a heredoc assignment with single-quoted 'EOF', with escaped linebreaks with ` in the content: if the second line has cd` command, I get back: ".sh: line X: cd: command not found"; but if I double-quote "EOF"; then bash variables ${A} do not get preserved as strings (they get expanded); but then, line-breaks are preserved - and, I don't have a problem running a command with cd in second line (and both 'EOF' and "EOF" seem to play well also with eval, for running a set of commands stored in a string variable). Cheers!
    – sdaau
    Commented May 24, 2012 at 8:08
  • 1
    ... and to add to my previous comment: bash comments "#" in double-qouted "EOF" variable, if called via eval $VAR, will cause all of the rest of the script to be commented, as here $VAR will be seen as a single line; to be able to use bash # comments in multiline script, double-quote also variable in the eval call: eval "$VAR"`.
    – sdaau
    Commented May 24, 2012 at 8:18
  • @sdaau: I had problems with eval ith this methods, but did not track it down since it was part of some package which evals some variables defined in it's config file. Error message was: /usr/lib/network/network: eval: line 153: syntax error: unexpected end of file. I just switched to another solution. Commented Apr 10, 2018 at 6:36
  • There are situations when you really genuinely want a here document, but if you are simply looking for how to put a newline in a static string, probably read stackoverflow.com/questions/3005963/… instead.
    – tripleee
    Commented Mar 28, 2022 at 5:03

15 Answers 15

727

You can avoid a useless use of cat and handle mismatched quotes better with this:

$ read -r -d '' VAR <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF

If you don't quote the variable when you echo it, newlines are lost. Quoting it preserves them:

$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

If you want to use indentation for readability in the source code, use a dash after the less-thans. The indentation must be done using only tabs (no spaces).

$ read -r -d '' VAR <<-'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
    EOF
$ echo "$VAR"
abc'asdf"
$(dont-execute-this)
foo"bar"''

If, instead, you want to preserve the tabs in the contents of the resulting variable, you need to remove tab from IFS. The terminal marker for the here doc (EOF) must not be indented.

$ IFS='' read -r -d '' VAR <<'EOF'
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''
EOF
$ echo "$VAR"
    abc'asdf"
    $(dont-execute-this)
    foo"bar"''

Tabs can be inserted at the command line by pressing Ctrl-V Tab. If you are using an editor, depending on which one, that may also work or you may have to turn off the feature that automatically converts tabs to spaces.

34
  • 209
    I think it's worth mentioning that if you have set -o errexit (a.k.a set -e) in your script and you use this then it will terminate your script because read returns a non-zero return code when it reaches EOF.
    – Mark Byers
    Commented Jun 28, 2011 at 8:04
  • 20
    @MarkByers: That's one of the reasons I never use set -e and always recommend against its use. It's better to use proper error handling instead. trap is your friend. Other friends: else and || among others. Commented Jun 29, 2011 at 3:37
  • 27
    Is the avoidance of cat really worth it in such a case? Assigning a heredoc to a variable with cat is a well known idiom. Somehow using read obfuscates things for little benefits imho. Commented Jan 7, 2014 at 21:18
  • 11
    @ulidtko That's because you don't have a space between d and the empty string; bash collapses -rd'' to simply -rd before read ever sees its arguments, so VAR is treated as the argument to -d.
    – chepner
    Commented Apr 1, 2014 at 19:16
  • 9
    In this format, read will return with a non-zero exit code. This makes this method less than ideal in a script with error checking enabled (eg set -e).
    – Swiss
    Commented Apr 28, 2015 at 22:08
411

Use $() to assign the output of cat to your variable like this:

VAR=$(
cat <<'END_HEREDOC'
abc'asdf"
$(dont-execute-this)
foo"bar"''
END_HEREDOC
)

# this will echo variable with new lines intact
echo "$VAR"
# this will echo variable without new lines (changed to space character)
echo $VAR

Making sure to delimit starting END_HEREDOC with single-quotes. This will prevent the content of the heredoc from being expanded, so dont-execute-this will not be executed.

Note that ending heredoc delimiter END_HEREDOC must be alone on the line (hence the ending parenthesis ) is on the next line).

Thanks to @ephemient for the answer.

18
  • 63
    +1. This is the most readable solution, at least for my eyes. It leaves the name of the variable at the far left of the page, instead of embedding it in the read command. Commented Apr 26, 2013 at 22:57
  • 25
    PSA: remember that the variable must be quoted to preserve newlines. echo "$VAR" instead of echo $VAR.
    – sevko
    Commented Nov 6, 2014 at 3:43
  • 8
    This is nice with ash and OpenWRT where read doesn't support -d. Commented Jan 19, 2015 at 4:22
  • 4
    For reasons I cannot fathom, this fails with an "unexpected EOF" error if you have an unpaired backtick in the heredoc. Commented Nov 1, 2016 at 0:31
  • 6
    @HubertGrzeskowiak it prevents variable expansion.
    – miken32
    Commented Nov 17, 2017 at 18:52
105

this is variation of Dennis method, looks more elegant in the scripts.

function definition (bash version):

define(){ IFS=$'\n' read -r -d '' ${1} || true; }

usage:

define VAR <<'EOF'
abc 'asdf " \$ $ \n $'\n'
$(dont-exec ute-this)
foo " bar " ' ' 

`bad bad ``
EOF

echo "$VAR"

Updated ksh 'read loop' version (also works on bash):

function define { typeset a o=; while IFS= read -r a; do o+="$a"$'\n'; done; o=${o%?}; eval "$1=\$o"; }

Tests (also includes versions that does not remove the last newline): https://gist.github.com/ootada/e80b6d7fb86acdcda75d77eb7ade364c

enjoy

10
  • 1
    This seems to work only superficially. The define function will return a status of 1, and I'm not quite sure what needs to be corrected.
    – fny
    Commented May 29, 2012 at 19:01
  • 1
    This is also superior to the accepted answer, because it can be modified to support POSIX sh in addition to bash (a read loop in the function, to avoid the -d '' bashism necessary to preserve newlines). Commented Dec 22, 2014 at 0:32
  • 2
    This solution works with set -e set, whereas the selected answer does not. It seems to be because of http://unix.stackexchange.com/a/265151/20650
    – ffledgling
    Commented Nov 8, 2016 at 11:30
  • 4
    @fny p.s. return status has long been fixed
    – ttt
    Commented Oct 4, 2017 at 15:54
  • 4
    ShellCheck SC2141 says it should be define(){ IFS=$'\n' ... (added $)
    – luckman212
    Commented Sep 27, 2020 at 5:36
56
VAR=<<END
abc
END

doesn't work because you are redirecting stdin to something that doesn't care about it, namely the assignment

export A=`cat <<END
sdfsdf
sdfsdf
sdfsfds
END
` ; echo $A

works, but there's a back-tic in there that may stop you from using this. Also, you should really avoid using backticks, it's better to use the command substitution notation $(..).

export A=$(cat <<END
sdfsdf
sdfsdf
sdfsfds
END
) ; echo $A
7
  • 3
    @l0st3d: So close... Use $(cat <<'END' instead. @Neil: The very last newline will not be part of the variable, but the rest will be preserved.
    – ephemient
    Commented Jul 22, 2009 at 20:16
  • 1
    It doesn't seem like any newlines are preserved. Running the above example I see: "sdfsdf sdfsdf sdfsfds"... ah! But writing echo "$A" (i.e. putting $A in double quotes) and you do see the newlines! Commented May 15, 2013 at 11:58
  • 2
    @Darren: aha! I had noticed the newlines issue, and using the quotes around the output variable does fix the issue. thx! Commented Jun 5, 2013 at 19:14
  • 2
    @l0st3d I would just leave out mention of backticks.
    – Brad Koch
    Commented May 21, 2014 at 16:03
  • 3
    Interestingly, due to the quirk of the first example, in a pinch you can use it for makeshift comment blocks like this: REM=<< 'REM' ... comment block goes here ... REM. Or more compactly, : << 'REM' .... Where "REM" could be something like "NOTES" or "SCRATCHPAD", etc.
    – Beejor
    Commented Dec 9, 2017 at 3:35
50

There is still no solution that preserves newlines.

This is not true - you're probably just being misled by the behaviour of echo:

echo $VAR # strips newlines

echo "$VAR" # preserves newlines

1
  • 9
    Really this is the behavior of how quoting a variable works. Without quotes, it will insert them as different parameters, space deliminated, while with quotes the entire variable contents will be treated as one argument
    – Czipperz
    Commented Nov 9, 2015 at 1:02
25

Branching off Neil's answer, you often don't need a var at all, you can use a function in much the same way as a variable and it's much easier to read than the inline or read-based solutions.

$ complex_message() {
  cat <<'EOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
EOF
}

$ echo "This is a $(complex_message)"
This is a abc'asdf"
$(dont-execute-this)
foo"bar"''
2
  • 5
    This solution is truly awesome. By far the most elegant IMHO.
    – noamtm
    Commented Jul 1, 2021 at 18:24
  • Lots of options here.
    – Cymatical
    Commented Sep 25, 2021 at 22:56
16

An array is a variable, so in that case mapfile will work

mapfile y <<'z'
abc'asdf"
$(dont-execute-this)
foo"bar"''
z

Then you can print like this

printf %s "${y[@]}"
2
10

I can't believe I'm the first to post this.

@Erman and @Zombo are close, but mapfile doesn't just read arrays...

Consider this:

#!/bin/bash
mapfile -d '' EXAMPLE << 'EOF'
Hello
こんにちは
今晩は
小夜なら
EOF
echo -n "$EXAMPLE"

Yielding:

Hello
こんにちは
今晩は
小夜なら

$'' is the delimiter given to mapfile, it will never occur, it means "not delimited".

So there's no need for a useless use of cat and no need to incur the penalty of recombining arrays.

Furthermore, you get this benefit:

$ echo $EXAMPLE
Hello こんにちは 今晩は 小夜なら

Which you do not receive with @Zombo's method:

mapfile y <<'z'
abc'asdf"
$(dont-execute-this)
foo"bar"''
z
echo $y
abc'asdf"

Bonus

If you run it through head -c -1 you can also get rid of that last newline in a way that won't be non-performant:

unset EXAMPLE
mapfile -d '' EXAMPLE < <(head -c -1 << EOF
Hello
こんにちは
今晩は
小夜なら
EOF
)
printf "%q" "$EXAMPLE"
$'Hello\nこんにちは\n今晩は\n小夜なら'
4
  • mapfile -d requires bash 4.4 or higher.
    – Martin
    Commented Nov 21, 2022 at 12:43
  • Oh, true. If the shell is older than bash 4.4 (Sat, 17 Sep 2016) this won't work, indeed. Commented Nov 21, 2022 at 22:12
  • 2
    Apple only ships bash 3.2 from 2006 :)
    – Martin
    Commented Nov 24, 2022 at 14:25
  • Good to know for those who use Apple computers. ;-) Commented Dec 19, 2022 at 20:17
3

assign a heredoc value to a variable

VAR="$(cat <<'VAREOF'
abc'asdf"
$(dont-execute-this)
foo"bar"''
VAREOF
)"

used as an argument of a command

echo "$(cat <<'SQLEOF'
xxx''xxx'xxx'xx  123123    123123
abc'asdf"
$(dont-execute-this)
foo"bar"''
SQLEOF
)"
2
  • When I tried the first method, there seems to be no line terminators between the lines. Must be some kind of configuration on my linux machine?
    – Kemin Zhou
    Commented Nov 24, 2015 at 18:46
  • 1
    This probably means when you were echoing your variable, you didn't put quotes around it... Try it like so: echo "$VAR"
    – Brad Parks
    Commented Feb 22, 2016 at 15:15
2

Thanks to dimo414's answer, this shows how his great solution works, and shows that you can have quotes and variables in the text easily as well:

example output

$ ./test.sh

The text from the example function is:
  Welcome dev: Would you "like" to know how many 'files' there are in /tmp?

  There are "      38" files in /tmp, according to the "wc" command

test.sh

#!/bin/bash

function text1()
{
  COUNT=$(\ls /tmp | wc -l)
cat <<EOF

  $1 Would you "like" to know how many 'files' there are in /tmp?

  There are "$COUNT" files in /tmp, according to the "wc" command

EOF
}

function main()
{
  OUT=$(text1 "Welcome dev:")
  echo "The text from the example function is: $OUT"
}

main
1
  • It would be interesting to see an unmatched quote in the text to see how it handles that. Maybe ` Don't freak out, there are "$COUNT" files` so the apostrophe/single quote can make things interesting.
    – dragon788
    Commented May 2, 2018 at 17:52
2

In many cases, this page has over-complex answers:

GNUV3="
    MyProg Copyright (C) 2024 NVRM 
    This program comes with ABSOLUTELY NO WARRANTY; for details type show w.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type show c for details.
"

echo "$GNUV3"
1
  • 1
    This will add an empty line before the string starts. To prevent this, add a backslash right after the opening quote: `GNUV3="\`
    – Panni
    Commented Apr 5 at 14:47
1

I found myself having to read a string with NULL in it, so here is a solution that will read anything you throw at it. Although if you actually are dealing with NULL, you will need to deal with that at the hex level.

$ cat > read.dd.sh

read.dd() {
     buf= 
     while read; do
        buf+=$REPLY
     done < <( dd bs=1 2>/dev/null | xxd -p )

     printf -v REPLY '%b' $( sed 's/../ \\\x&/g' <<< $buf )
}

Proof:

$ . read.dd.sh
$ read.dd < read.dd.sh
$ echo -n "$REPLY" > read.dd.sh.copy
$ diff read.dd.sh read.dd.sh.copy || echo "File are different"
$ 

HEREDOC example (with ^J, ^M, ^I):

$ read.dd <<'HEREDOC'
>       (TAB)
>       (SPACES)
(^J)^M(^M)
> DONE
>
> HEREDOC

$ declare -p REPLY
declare -- REPLY="  (TAB)
      (SPACES)
(^M)
DONE

"

$ declare -p REPLY | xxd
0000000: 6465 636c 6172 6520 2d2d 2052 4550 4c59  declare -- REPLY
0000010: 3d22 0928 5441 4229 0a20 2020 2020 2028  =".(TAB).      (
0000020: 5350 4143 4553 290a 285e 4a29 0d28 5e4d  SPACES).(^J).(^M
0000030: 290a 444f 4e45 0a0a 220a                 ).DONE
0
0

This will work in any POSIX shell, avoids the issue of the non-zero exit status of read, and looks cleaner than an inline cat.

def() {
    eval "$1=\$(cat)"
}

def variable << '>>'
abc'asdf"
$(dont-execute-this)
foo"bar"''
>>

Caveat: This will strip newlines at the end of the here-doc. Newlines that are not at the end will be preserved.

-1

Here's a way to do it that is (imho) quite elegant and avoids a UUOC:

  VAR=$(sed -e 's/[ ]*\| //g' -e '1d;$d' <<'--------------------'
      | 
      | <!DOCTYPE html>
      | <html>
      |   <head>
      |     <script src='script.js'></script>
      |   </head>
      |   <body>
      |     <span id='hello-world'></span>
      |   </body>
      | </html>
      | 
--------------------
    )

The '|' characters define the margin, and only the whitespace to the right of the margin is respected in the printed string. The '1d;$d' strips the first and last line, which are just added as a top and bottom margin around the content. Everything can be indented to whatever level you like, except the HEREDOC delimiter, which in this case is just a bunch of hyphens.

echo "$VAR"

# prints

<!DOCTYPE html>
<html>
  <head>
    <script src='script.js'></script>
  </head>
  <body>
    <span id='hello-world'></span>
  </body>
</html>
1
  • Um, dude, good attempt but clearly compiling a regular expression and running a global replace is far more resource-hungry of an operation than a UUOC. The mapfile approach is best per appearance and performance, this one is way out there (why the margin??) Commented Aug 16, 2022 at 8:31
-12
$TEST="ok"
read MYTEXT <<EOT
this bash trick
should preserve
newlines $TEST
long live perl
EOT
echo -e $MYTEXT
2
  • Even with the various errors fixed, this only places the first line of the here document in MYTEXT
    – tripleee
    Commented Mar 28, 2022 at 4:54
  • Easy to fix, the idea is ok! I used read -d $'\000' MYTEXT <<EOT and worked well...
    – TrueY
    Commented Sep 12, 2022 at 12:56

Not the answer you're looking for? Browse other questions tagged or ask your own question.