Is there something similar to echo -n in heredoc (EOF)?

Solution 1:

No, heredoc always ends in a newline. But you can still remove the last newline, for example with Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -p reads the input line by line, runs the code specified in -e, and prints the (possibly modified) line
  • chomp removes the final newline if it exists, eof returns true at the end of file.

Solution 2:

Is there a way having a heredoc behave similar to echo -ne without the end-of-line-symbol?

Short answer is that heredoc and herestrings are just built that way, so no - here-doc and herestring will be adding a trailing newline always and there's no option or native way to disable it ( perhaps there will be in future bash releases?).

However, one way to approach it via bash-only ways would be to read what heredoc gives via the standard while IFS= read -r method, but add a delay and print last line via printf without the trailing newline. Here's what one could do with bash-only:

#!/usr/bin/env bash

while true
do
    IFS= read -r line || { printf "%s" "$delayed";break;}
    if [ -n "$delayed" ]; 
    then
        printf "%s\n" "$delayed"
    fi
    delayed=$line
done <<EOF
one
two
EOF

What you see here is the usual while loop with IFS= read -r line approach, however each line is stored into a variable and thus delayed ( so we skip printing first line, store it, and start printing only when we've read second line). That way we can capture last line, and when on next iteration read returns exit status 1, that's when we print last line without newline via printf. Verbose ? yes. But it works:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Notice the $ prompt character being pushed forward, since there's no newline. As a bonus, this solution is portable and POSIX-ish, so it should work in ksh and dash as well.

$ dash ./strip_heredoc_newline.sh                                 
one
two$

Solution 3:

I recommend you try a different approach. Rather than piecing together your generated script from multiple echo statements and heredocs. I suggest you try to use a single heredoc.

You can use variable expansion and command substitution inside a heredoc. Like in this example:

#!/bin/bash
cat <<EOF
$USER $(uname)
EOF

I find that this often leads to much more readable end results than producing the output piece by piece.

Also it looks like you have been forgetting the #! line in the beginning of your scripts. The #! line is mandatory in any correctly formatted scripts. Attempting to run a script without the #! line will only work if you call it from a shell that works around badly formatted scripts, and even in that case it might end up getting interpreted by a different shell than you intended.

Solution 4:

Heredocs always end in a new-line characters but you can simply remove that. The easiest way I know is with the head program:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF

Solution 5:

You could remove the newlines in whatever way you like, piping to tr would probably be simplest. This would remove all newlines, of course.

$ tr -d '\n' <<EOF 
if ((
EOF

But, the if statement you're building doesn't require that, the arithmetic expression (( .. )) should work fine even if it contains newlines.

So, you could do

cat <<EOF 
if ((
EOF
for name in x y; do 
    cat << EOF
    ( \$${name}_E != 0 && \$${name}_E != 1 ) ||
EOF
done
cat <<EOF
    0 )) ; then 
    echo do something
fi
EOF

producing

if ((
    ( $x_E != 0 && $x_E != 1 ) ||
    ( $y_E != 0 && $y_E != 1 ) ||
    0 )) ; then 
    echo do something
fi

Building it in a loop still makes ugly, though. The || 0 is there of course so that we don't need to special case the first or the last line.


Also, you have that | sudo tee ... in every output. I think we could get rid of that by opening a redirection only once (with process substitution), and using that for the later printing:

exec 3> >(sudo tee outputfile &>/dev/null)
echo output to the pipe like this >&3
echo etc. >&3
exec 3>&-         # close it in the end

And frankly, I still think that building variable names from variables in the outer script (like that \$${name}_E ) is a bit ugly, and it should probably be replaced with an associative array.