Search code examples
bashansibleescapingheredoc

Ansible quoted heredoc in expect/command block not finding delimiter / multiline command in expect command


I'm trying to generate a script to be copied in an ansible expect/response block with ssh to the remote host. I'd like to avoid writing intermediate files.

I have two issues:

  1. Ansible seems to do some processing in command which messes up multiline bash scripts, but I can get around it with /bin/bash -c 'my \ multi \ line \ command' (on actual multiple lines)
  2. I can't seem to quote the EOF mark for the heredoc correctly within the bash invocation (see below)
  - name: Generate script on the fly
    expect: 
      command: |
        /bin/bash -c 'ssh -o PubkeyAuthentication=no -p {{ ansible_ssh_port }} {{ ansible_user }}@{{ ansible_host }} "cat - > {{ tgtdir }}/myscript.sh" <<-'EOF' 
          #! /bin/env sh
          a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; THIS=$(cd "$a"; pwd)
          echo "Script dir == ${THIS}"
          echo "{{ someansiblevar }}"
          EOF
        '
      responses:
        (.*)password: "{{ ansible_ssh_pass }}"
    delegate_to: localhost

(Note that in this example I'm apparently getting away with using single quotes within single quotes, but other variations all fail as well.)

I have tried to escape the quotes around the first EOF in different ways, but I always get the warning:

"stdout_lines": ["/bin/bash: line 10: warning: here-document at line 0 delimited by end-of-file (wanted `EOF')", "", "[email protected]'s password: "]

And the contents of myscript.sh are either not correctly left alone (i.e. all $... expanded), or contain the last EOF (since it is not recognized as delimiter and is just reading to the end of the command block, hence the warning.

What is the correct way of handling this?

(Note that I delegate to localhost because I don't want to rely on python on the target host, these are minimal systems with only ssh).


Solution

  • Move the closing EOF left by two spaces. At the moment it does not start at the beginning of its line, so bash won't see it as a delimiter.

    From the bash man-page:

    Here Documents

    This type of redirection instructs the shell to read input from the current source until a line containing only delimiter (with no trailing blanks) is seen. All of the lines read up to that point are then used as the standard input for a command. The format of here-documents is:

    <<[-]word
            here-document 
    delimiter 
    

    No parameter expansion, command substitution, arithmetic expansion, or pathname expansion is performed on word. If any characters in word are quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion. In the latter case, the character sequence \<newline> is ignored, and \ must be used to quote the characters \, $, and `. If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter. This allows here-documents within shell scripts to be indented in a natural fashion.

    So you'll either need to remove the indentation from the EOF line or indent everything with TABs instead of spaces. I suggest that the former option is simpler.