135

I need to write some complex xml to a variable inside a bash script. The xml needs to be readable inside the bash script as this is where the xml fragment will live, it's not being read from another file or source.

So my question is this if I have a long string which I want to be human readable inside my bash script what is the best way to go about it?

Ideally I want:

  • to not have to escape any of the characters
  • have it break across multiple lines making it human readable
  • keep it's indentation

Can this be done with EOF or something, could anyone give me an example?

e.g.

String = <<EOF
 <?xml version="1.0" encoding='UTF-8'?>
 <painting>
   <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
   <caption>This is Raphael's "Foligno" Madonna, painted in
   <date>1511</date>-<date>1512</date>.</caption>
 </painting>
EOF
2

5 Answers 5

172

This will put your text into your variable without needing to escape the quotes. It will also handle unbalanced quotes (apostrophes, i.e. '). Putting quotes around the sentinel (EOF) prevents the text from undergoing parameter expansion. The -d'' causes it to read multiple lines (ignore newlines). read is a Bash built-in so it doesn't require calling an external command such as cat.

IFS='' read -r -d '' String <<"EOF"
<?xml version="1.0" encoding='UTF-8'?>
 <painting>
   <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
   <caption>This is Raphael's "Foligno" Madonna, painted in
   <date>1511</date>-<date>1512</date>.</caption>
 </painting>
EOF
22
  • 6
    cat is an external command. Not using it saves doing that. Plus, some have the philosophy that if you're using cat with fewer than two arguments "Ur doin' it wrong" (which is distinct from "useless use of cat"). Oct 9, 2009 at 0:03
  • 13
    and never ever indent second EOF.... (multiple table to head bangs involved)
    – IljaBek
    May 18, 2012 at 19:40
  • 12
    I tried to use the above statement while set -e. It seems read always returns non-zero. You can thick this behaviour by using ! read -d .......
    – krissi
    Nov 8, 2012 at 10:56
  • 3
    @DennisWilliamson: "Proper" error handling in shell is prohibitively tedious. set -e is imperfect and lays the occasional trap, but makes scripts far more reliable.
    – Andrew
    Nov 19, 2012 at 22:42
  • 13
    And if you are using this multi-line String variable to write to a file, put the variable around "QUOTES" like echo "${String}" > /tmp/multiline_file.txt or echo "${String}" | tee /tmp/multiline_file.txt. Took me more than an hour to find that.
    – Aditya
    Apr 20, 2014 at 16:16
42

You've been almost there. Either you use cat for the assembly of your string or you quote the whole string (in which case you'd have to escape the quotes inside your string):

#!/bin/sh
VAR1=$(cat <<EOF
<?xml version="1.0" encoding='UTF-8'?>
<painting>
  <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
  <caption>This is Raphael's "Foligno" Madonna, painted in
  <date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF
)

VAR2="<?xml version=\"1.0\" encoding='UTF-8'?>
<painting>
  <img src=\"madonna.jpg\" alt='Foligno Madonna, by Raphael'/>
  <caption>This is Raphael's \"Foligno\" Madonna, painted in
  <date>1511</date>-<date>1512</date>.</caption>
</painting>"

echo "${VAR1}"
echo "${VAR2}"
5
  • 1
    Unfortunately, the apostrophe in "Raphael's" makes the first one not work. Oct 8, 2009 at 12:37
  • Both assignments work for me eventually. The single quote in VAR1 should not be a problem (at least not for bash). Maybe you have been misled by the syntax highlighting?
    – joschi
    Oct 8, 2009 at 21:13
  • 2
    It works in a script, but not at a Bash prompt. Sorry for not being clearer. Oct 9, 2009 at 6:08
  • 2
    It is better to quote EOF as 'EOF' or "EOF", otherwise shell variables will be parsed. May 6, 2018 at 13:43
  • 1
    Be sure to echo "$VAR1" with quotes to inspect your work or newlines will not print.
    – rjurney
    Sep 24, 2020 at 3:04
21
#!/bin/sh

VAR1=`cat <<EOF
<?xml version="1.0" encoding='UTF-8'?>
<painting>
  <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
  <caption>This is Raphael's "Foligno" Madonna, painted in
  <date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF
`
echo "VAR1: ${VAR1}"

This should work fine within Bourne shell environment

4
  • 3
    +1 this solution allow variable substitution like ${foo}
    – Offirmo
    Sep 27, 2012 at 16:54
  • Upside: sh-compatible. Downside: backticks are deprecated/discouraged in bash. Now if I had to choose between sh and bash...
    – Zenexer
    Sep 26, 2013 at 22:55
  • 2
    since when are backticks deprecated/discouraged? just curious Apr 21, 2018 at 1:50
  • @AlexanderMills shellcheck.net/wiki/SC2006 Mar 13 at 5:43
6

Yet another way to do the same...

I like to use variables and special <<- who drop tabulation at begin of each lines to permit script indentation:

#!/bin/bash

mapfile Pattern <<-eof
        <?xml version="1.0" encoding='UTF-8'?>
        <painting>
          <img src="%s" alt='%s'/>
          <caption>%s, painted in
          <date>%s</date>-<date>%s</date>.</caption>
        </painting>
        eof

while IFS=";" read file alt caption start end ;do
    printf "${Pattern[*]}" "$file" "$alt" "$caption" "$start" "$end"
  done <<-eof
        madonna.jpg;Foligno Madonna, by Raphael;This is Raphael's "Foligno" Madonna;1511;1512
        eof

warning: there is no blank space before eof but only tabulation.

<?xml version="1.0" encoding='UTF-8'?>
 <painting>
   <img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
   <caption>This is Raphael's "Foligno" Madonna, painted in
   <date>1511</date>-<date>1512</date>.</caption>
 </painting>
Some explanations:
  • mapfile read entire here document in an array.
  • the syntaxe "${Pattern[*]}" do cast this array into a string.
  • I use IFS=";" because there is no ; in required strings
  • The syntaxe while IFS=";" read file ... prevent IFS to be modified for the rest of the script. In this, only read do use the modified IFS.
  • no fork.
2
  • Note that mapfile requires Bash 4 or higher. And the syntax "${Pattern[*]}" casts the array into a string when in quotes (as shown in the example code). Jul 22, 2013 at 23:54
  • Yes, bash 4 was very new when this question was asked. Jul 23, 2013 at 2:31
2

There are too many corner cases in many of the other answers.

To be absolutely sure there are no issues with spaces, tabs, IFS etc., a better approach is to use the "heredoc" construct, but encode the contents of the heredoc using uuencode as explained here:

https://stackoverflow.com/questions/6896025/#11379627.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .