Reputation: 10882
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 $?
...
#!/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}"
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
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
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
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
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