Robottinosino
Robottinosino

Reputation: 10882

Reading a Bash variable from a multiline Here Document

What is the perfect way to read a multiline variable from a Here Document in Bash?

Look at how my way of doing it has the side-effect of returning 1 as $?...


Bash code:

#!/bin/bash

printf "Unsetting...\n"
unset variable
printf "Exit status: %s.\n" "$?"
printf "Variable: %s.\n" "${variable}"

printf "\n"

printf "Setting to 'foo'...\n"
variable='foo'
printf "Exit status: %s.\n" "$?"
printf "Variable: %s.\n" "${variable}"

printf "\n"

printf "Setting to 'bar' by reading stdin from process substitution...\n"
read variable < <(echo 'bar')
printf "Exit status: %s.\n" "$?"
printf "Variable: %s.\n" "${variable}"

printf "\n"

printf "Setting to 'baz' by reading stdin from Here String...\n"
here_string='baz'
read variable <<< "${here_string}"
printf "Exit status: %s.\n" "$?"
printf "Variable: %s.\n" "${variable}"

printf "\n"

printf "Setting to 'quux' by reading stdin from Here Document...\n"
read variable <<- 'EOF'
quux
EOF
printf "Exit status: %s.\n" "$?"
printf "Variable: %s.\n" "${variable}"

printf "\n"

printf "Setting to 'thud <newline> thud' by reading stdin from Here Document...\n"
read -d '' variable <<- 'EOF'
thud
thud
EOF
printf "Exit status: %s.\n" "$?" # ONE?!one!!oneone? :)
printf "Variable: %s.\n" "${variable}"

Output

Unsetting...
Exit status: 0.
Variable: .

Setting to 'foo'...
Exit status: 0.
Variable: foo.

Setting to 'bar' by reading stdin from process substitution...
Exit status: 0.
Variable: bar.

Setting to 'baz' by reading stdin from Here String...
Exit status: 0.
Variable: baz.

Setting to 'quux' by reading stdin from Here Document...
Exit status: 0.
Variable: quux.

Setting to 'thud <newline> thud' by reading stdin from Here
Document...
Exit status: 1.
Variable: thud
thud.

Upvotes: 2

Views: 2669

Answers (4)

mariux
mariux

Reputation: 3117

You can read the here-document into an array with mapfile or its alias readarray

mapfile var <<-'EOF'
thud
thud
EOF

resulting in

var=([0]="thud
" [1]="thud
")

and then just concatenate the array elements e.g.

variable=${var[0]}
variable+=${var[1]}
...

For arrays of variable length you can do this in a loop or use the printf builtin function (thanks to eush77):

printf -v variable "%s" "${var[@]}"

both options result in

variable=$'thud\nthud\n'

The reason why the exit status was 1 was already given by Sorpigal ..

Upvotes: 3

ormaaj
ormaaj

Reputation: 6577

read returns 1 when it reaches EOF, at the same time as assigning the last result read. This is why files that don't end with a newline are problematic, because a simple loop with read will not run the loop body for the final line.

When you use read -d '' without putting a NUL byte into the input, this is just like reading the last line of a file. The question is why would you ever want read to return 0 in this case? It's doing just like it should. Also note that heredocs (and herestrings) always add a newline to the end automatically.

Somewhat related:

Upvotes: 4

sorpigal
sorpigal

Reputation: 26086

read will return nonzero if it reaches the end of a file without hitting its delimiter. Apparently \0 is treated as EOF whether it's your delimiter or not. One hack to prevent this would be to choose an unlikely delimiter and pad your string with it at the end. Example:

read -r -d $'\3' variable <<<"thud\nthud\n"$'\3' ; echo $?

That said, however, this is harder to do in a heredoc.

Upvotes: 3

chepner
chepner

Reputation: 531135

I think the problem is that read is seeing end-of-file before it sees an ASCII NUL character. However, if that were true, the following should exit with 0:

read -d '' variable <<< $'thud\nthud\x00'

but it doesn't.

In lieu of a real fix, I can offer the following hack. Since it prevents any command from actually returning 0, it shouldn't break your login script:

read -d '' variable <<-'EOF' || true
thud
thud
EOF

read still exits 1, but this merely causes the shell to execute the true command, which is guaranteed to exit 0.

Upvotes: 5

Related Questions