[bash] here-document gives 'unexpected end of file' error

I need my script to send an email from terminal. Based on what I've seen here and many other places online, I formatted it like this:

/var/mail -s "$SUBJECT" "$EMAIL" << EOF
Here's a line of my message!
And here's another line!
Last line of the message here!
EOF

However, when I run this I get this warning:

myfile.sh: line x: warning: here-document at line y delimited by end-of-file (wanted 'EOF')

myfile.sh: line x+1: syntax error: unexpected end of file

...where line x is the last written line of code in the program, and line y is the line with /var/mail in it. I've tried replacing EOF with other things (ENDOFMESSAGE, FINISH, etc.) but to no avail. Nearly everything I've found online has it done this way, and I'm really new at bash so I'm having a hard time figuring it out on my own. Could anyone offer any help?

This question is related to bash email heredoc

The answer is


Note one can also get this error if you do this;

while read line; do
  echo $line
done << somefile

Because << somefile should read < somefile in this case.


When I want to have docstrings for my bash functions, I use a solution similar to the suggestion of user12205 in a duplicate of this question.

See how I define USAGE for a solution that:

  • auto-formats well for me in my IDE of choice (sublime)
  • is multi-line
  • can use spaces or tabs as indentation
  • preserves indentations within the comment.
function foo {
    # Docstring
    read -r -d '' USAGE <<'    END'
        # This method prints foo to the terminal.
        #
        # Enter `foo -h` to see the docstring.
        #      It has indentations and multiple lines.
        #
        # Change the delimiter if you need hashtag for some reason.
        # This can include $$ and = and eval, but won't be evaluated
    END


    if [ "$1" = "-h" ]
    then
        echo "$USAGE" | cut -d "#" -f 2 | cut -c 2-
        return
    fi

    echo "foo"
}

So foo -h yields:

This method prints foo to the terminal.

Enter `foo -h` to see the docstring.
     It has indentations and multiple lines.

Change the delimiter if you need hashtag for some reason.
This can include $$ and = and eval, but won't be evaluated

Explanation

cut -d "#" -f 2: Retrieve the second portion of the # delimited lines. (Think a csv with "#" as the delimiter, empty first column).

cut -c 2-: Retrieve the 2nd to end character of the resultant string

Also note that if [ "$1" = "-h" ] evaluates as False if there is no first argument, w/o error, since it becomes an empty string.


Along with the other answers mentioned by Barmar and Joni, I've noticed that I sometimes have to leave a blank line before and after my EOF when using <<-EOF.


Please try to remove the preceeding spaces before EOF:-

/var/mail -s "$SUBJECT" "$EMAIL" <<-EOF

Using <tab> instead of <spaces> for ident AND using <<-EOF works fine.

The "-" removes the <tabs>, not <spaces>, but at least this works.


Here is a flexible way to do deal with multiple indented lines without using heredoc.

  echo 'Hello!'
  sed -e 's:^\s*::' < <(echo '
    Some indented text here.
    Some indented text here.
  ')
  if [[ true ]]; then
    sed -e 's:^\s\{4,4\}::' < <(echo '
      Some indented text here.
        Some extra indented text here.
      Some indented text here.
    ')
  fi

Some notes on this solution:

  • if the content is expected to have simple quotes, either escape them using \ or replace the string delimiters with double quotes. In the latter case, be careful that construction like $(command) will be interpreted. If the string contains both simple and double quotes, you'll have to escape at least of kind.
  • the given example print a trailing empty line, there are numerous way to get rid of it, not included here to keep the proposal to a minimum clutter
  • the flexibility comes from the ease with which you can control how much leading space should stay or go, provided that you know some sed REGEXP of course.

The line that starts or ends the here-doc probably has some non-printable or whitespace characters (for example, carriage return) which means that the second "EOF" does not match the first, and doesn't end the here-doc like it should. This is a very common error, and difficult to detect with just a text editor. You can make non-printable characters visible for example with cat:

cat -A myfile.sh

Once you see the output from cat -A the solution will be obvious: remove the offending characters.


Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to email

Monitoring the Full Disclosure mailinglist require(vendor/autoload.php): failed to open stream Failed to authenticate on SMTP server error using gmail Expected response code 220 but got code "", with message "" in Laravel How to to send mail using gmail in Laravel? Laravel Mail::send() sending to multiple to or bcc addresses Getting "The remote certificate is invalid according to the validation procedure" when SMTP server has a valid certificate How to validate an e-mail address in swift? PHP mail function doesn't complete sending of e-mail How to validate email id in angularJs using ng-pattern

Examples related to heredoc

How to cat <<EOF >> a file containing code? here-document gives 'unexpected end of file' error What is the advantage of using heredoc in PHP? Using variables inside a bash heredoc How can I write a heredoc to a file in Bash script? How does "cat << EOF" work in bash? Executing multi-line statements in the one-line command-line? How to assign a heredoc value to a variable in Bash? Creating multiline strings in JavaScript