How do I bring HEREDOC text into a shell script variable?
The problem is that, in Bash, inside $( ... )
escape (and other) sequences get parsed, even though the heredoc itself wouldn't have them. You get a doubled line because \
escapes the line break. What you're seeing is really a parsing issue in Bash - other shells don't do this. Backticks can also be a problem in older versions. I have confirmed that this is a bug in Bash, and it will be fixed in future versions.
You can at least simplify your function drastically:
func() {
res=$(cat)
}
func <<'HEREDOC'
...
HEREDOC
If you want to choose the output variable it can be parameterised:
func() {
eval "$1"'=$(cat)'
}
func res<<'HEREDOC'
...
HEREDOC
Or a fairly ugly one without eval
:
{ res=$(cat) ; } <<'HEREDOC'
...
HEREDOC
The {}
are needed, rather than ()
, so that the variable remains available afterwards.
Depending on how often you'll do this, and to what end, you might prefer one or another of these options. The last one is the most concise for a one-off.
If you're able to use zsh
, your original command substitution + heredoc will work as-is, but you can also collapse all of this down further:
x=$(<<'EOT'
...
EOT
)
Bash doesn't support this and I don't think any other shell that would experience the problem you're having does either.
About the OP solution:
You do not need an eval to assign a variable if you allow some constant variable to be used.
the general structure of calling a function that receives the HEREDOC could also be implemented.
A solution that works in all (reasonable) shells with both items solved is this:
#!/bin/bash
nl="
"
read_heredoc(){
var=""
while IFS="$nl" read -r line; do
var="$var$line$nl"
done
}
read_heredoc <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
read_heredoc2_result="$str"
printf '%s' "${read_heredoc2_result}"
A solution for the original question.
A solution that works since bash 2.04 (and recent zsh, lksh, mksh).
Look below for a more portable (POSIX) version.
#!/bin/bash
read_heredoc() {
IFS='' read -d '' -r var <<'HEREDOC'
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
}
read_heredoc
echo "$var"
The core command
IFS='' read -d '' -r var <<'HEREDOC'
works as follows:
- The word
HEREDOC
is (single) quoted to avoid any expansion of the text that follows. - The "here doc" contents are served in the stdin with
<<
. - The option
-d ''
forcesread
to slurp the whole content of the "here doc". - The
-r
option avoids interpretation of backslash quoted characters. - The core command is similar to
read var
. - And the last detail is
IFS=''
, which will avoid that read remove leading or trailing characters in the default IFS: spacetabnewline.
In ksh, the null value for the -d ''
option doesn't work.
As a workaround, if the text has no "carriage return", a -d $'\r'
works (if a $'\r'
is added to the end of each line, of course).
An added (in comments) requirement is to generate a POSIX compliant solution.
POSIX
Extending the idea to make it run only with POSIX options.
That means mainly no -d
for read
. That forces a read for each line.
That, in turn forces the need to capture a line at a time.
Then, to build the var
a trailing new line must be added (as the read removed it).
#!/bin/sh
nl='
'
read_heredoc() {
unset var
while IFS="$nl" read -r line; do
var="$var$line$nl"
done <<\HEREDOC
_ _ _
| | | (_)
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | |_ _ __ ___
| '_ ` _ \| | | | '_ \| |/ _` |/ __/ _ \/ _ \| '_ \| | | '_ \ / _ \
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\__, | .__/|_|\__,_|\___\___|\___/|_| |_|_|_|_| |_|\___|
__/ | |
|___/|_|
HEREDOC
}
read_heredoc
printf '%s' "$var"
That works (and has been tested) in all reasonable shells.
Useless use of cat (quote \ and `):
myplaceonline="
_ _ _
_ __ ___ _ _ _ __ | | __ _ ___ ___ ___ _ __ | (_)_ __ ___
| '_ \` _ \\| | | | '_ \\| |/ _\` |/ __/ _ \\/ _ \\| '_ \\| | | '_ \\ / _ \\
| | | | | | |_| | |_) | | (_| | (_| __/ (_) | | | | | | | | | __/
|_| |_| |_|\\__, | .__/|_|\\__,_|\\___\\___|\\___/|_| |_|_|_|_| |_|\\___|
|___/|_
"
Or without quoting:
myplaceonline="$(figlet myplaceonline)"