51

Is it possible to pass a here document as a bash function argument, and in the function have the parameter preserved as a multi-lined variable?

Something along the following lines:

function printArgs {
echo arg1="$1"
echo -n arg2=
cat <<EOF
$2
EOF
}

printArgs 17 <<EOF
18
19
EOF

or maybe:

printArgs 17 $(cat <<EOF
18
19
EOF)

I have a here document that I want to feed to ssh as the commands to execute, and the ssh session is called from a bash function.

Niebelung
  • 513
  • 1
  • 4
  • 4

5 Answers5

39

The way to that would be possible is:

printArgs 17 "$(cat <<EOF
18
19
EOF
)"

But why would you want to use a heredoc for this? heredoc is treated as a file in the arguments so you have to (ab)use cat to get the contents of the file, why not just do something like:

print Args 17 "18
19"

Please keep in mind that it is better to make a script on the machine you want to ssh to and run that then trying some hack like this because bash will still expand variables and such in your multiline argument.

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
Robokop
  • 906
  • 1
  • 5
  • 12
  • 1
    Maybe there should be a newline between `EOF` and `("`? – Vi. Sep 14 '16 at 14:31
  • 2
    I want to pass a big ugly form for Emacs to evaluate in batch mode; this is a perfect use case for this "hack". – Resigned June 2023 Aug 18 '17 at 03:14
  • And months later I need to pass a big ugly form for GIMP to evaluate in its TinyScheme batch processor. It just seems to keep coming up <3 – Resigned June 2023 Jan 05 '18 at 04:58
  • 3
    Note also that you can suppress variable expansion in heredocs using `<<"EOF"` instead of `< – Resigned June 2023 Mar 14 '18 at 23:18
  • 9
    To answer why this is helpful: HEREDOCs permit me to use both " and ' in my strings without escaping the quotation. – Michael Mol Sep 13 '18 at 01:41
  • Works great, also the `$(` and `)` can be replaced by backticks according to this answer https://unix.stackexchange.com/a/518934/191475 - it seems to be equivalent in function but better for Intellij Idea editor (without backticks the editor was confused in recognition of inner/outer area of quotes for syntax highlighting). – Tomáš Záluský Jan 13 '22 at 10:27
20

If you're not using something that will absorb standard input, then you will have to supply something that does it:

$ foo () { while read -r line; do var+=$line; done; }
$ foo <<EOF
a
b
c
EOF
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 6
    Just a FYI. You can also do "cat /dev/stdin" in the function instead of iterating with "while" – joebeeson Jun 11 '15 at 13:12
  • Although this is correct in line of principle, it will eat newlines, which need to be appended for each new `$line`. Also, the variable should be initialized as `local`. – Marcus Aug 21 '17 at 12:27
9

Building on Ned's answer, my solution allows the function to take its input as an argument list or as a heredoc.

printArgs() (
  [[ $# -gt 0 ]] && exec <<< $*
  ssh -T remotehost
)

So you can do this

printArgs uname

or this

printArgs << EOF
uname
uptime
EOF

So you can use the first form for single commands and the long form for multiple commands.

starfry
  • 9,273
  • 7
  • 66
  • 96
  • 1
    Cute. Very, very cute. – Charles Duffy Dec 15 '15 at 00:50
  • I think I need to brush up on `exec`. In your example, it seems like the commands would execute on the local machine, not on the remote one. @starfry – mattalxndr Nov 20 '21 at 13:34
  • They definitely execute on the remote machine (replace `remotehost` with something real that you can access and try the above example). Just in case your shell makes a difference, use `bash` (as per the original question). If your shell does make a difference, probably worth adding a note here. – starfry Nov 22 '21 at 09:04
  • 1
    @mattalxndr If `exec` has no *command* argument (as is the case here), any stdio redirections applied to it will apply to the current shell, i.e. will apply here also to the following `ssh` line. In other words, exec feeds the expanded $* into stdin of ssh. However the disadvantage of this approach is that you may get the motd greeting from remotehost displayed, as ssh runs an interactive login shell, not a command. – Markus Kuhn Nov 22 '21 at 19:10
  • I see, so it's feeding stdin. Interesting. – mattalxndr Nov 24 '21 at 03:15
3

xargs should do exactly what you want. It convert standard input to argument for a command (notice -0 allow to preserve newlines)

$ xargs -0 <<EOF printArgs 17
18
19
EOF

But for you special case, I suggest you to send command on standard input of ssh:

$ ssh host <<EOF
ls
EOF
Jérôme Pouiller
  • 9,249
  • 5
  • 39
  • 47
0

One way to feed commands to ssh through a here doc and a function is as so:

#!/bin/sh
# define the function
printArgs() {
echo "$1"
ssh -T remotehost
}
# call it with a here document supplying its standard input
printArgs 17 <<EOF
uname
uptime
EOF

The results:

17
Linux remotehost 2.6.32-5-686 ...
Last login: ...
No mail.
Linux
 16:46:50 up 4 days, 17:31,  0 users,  load average: 0.06, 0.04, 0.01
Ned Deily
  • 83,389
  • 16
  • 128
  • 151