0

I am trying to make a function in bash to automatize sftp. Something like:

sftp host:dir <<EOF
put afile.txt
exit
EOF

The problem is I need the last part as a variable. So something like:

file="<<EOF
put afile.txt
exit
EOF"

so I can use it like

stfp host:dir $file 

This clearly doesn't work. I have two problems I don't know to solve: making the variable $file in one line and adding the variable to the command.

Any help would be appreciated

PS: Sorry for being picky. I have tried the options with echo -n $var | stfp and sftp -b script_file. But I am looking for something that takes an argument in the latest position and could be written in one line.

  • Option `-b` (Batch mode) might help. – Cyrus Jun 29 '21 at 19:17
  • 1
    Variable expansions aren't parsed as syntax, so it doesn't matter if you can put `< – Charles Duffy Jun 29 '21 at 19:20
  • 3
    Put the data in a file; redirect standard input from the file (make sure the file gets cleaned up afterwards, and if anything goes wrong). The here-document basically does that — puts the material in the here-document into a file that is given as standard input to the process. – Jonathan Leffler Jun 29 '21 at 19:20
  • 2
    A herestring is really the sanest approach -- put _only the literal data_, not the surrounding syntax, in a variable; and then use `<<<"$that_variable"`. – Charles Duffy Jun 29 '21 at 19:21
  • 1
    ...the reason putting `< – Charles Duffy Jun 29 '21 at 19:24
  • `stfp host:dir "$file"` would work fine if he prefixed it with a command beginning with "e". – Cyrus Jun 29 '21 at 19:34
  • 1
    `echo -n $var` has its own bugs; you need to quote to make it work correctly (`echo -n "$var"` could work though it's better not to use the `-n`; but big-picture, when you leave out the quotes, `echo -n $var` throws away newlines, expands globs, and does other things you don't want) – Charles Duffy Jun 29 '21 at 19:44
  • 1
    @OddKviteberg, re: [your recent edit](https://stackoverflow.com/revisions/68184448/3) ("Sorry for being picky"), do note the links above. _There exists no possible content_ that can be stored in the variable named `file` that will make `sftp host:dir $file` write an arbitrary string to stdin of `sftp`. You're going to need to focus more on your desired outcome, and less on your desired mechanism to get there. – Charles Duffy Jun 29 '21 at 19:45
  • @OddKviteberg, ...to provide a terser explanation of why that's true, see the [BashParser](https://mywiki.wooledge.org/BashParser) wiki page. Note that `$file` is only replaced with the variable's content in step 5 of the parser, which means that steps 1-4 can't see the result of such replacements. (This is a good thing, really -- bash could never handle untrusted data safely if such data could be silently/unintentionally interpreted as code; as for _explicitly, intentionally_ interpreting data as code, see [BashFAQ #48](http://mywiki.wooledge.org/BashFAQ/048)). – Charles Duffy Jun 29 '21 at 19:51
  • @CharlesDuffy well the solution you proposed was almost what I am looking for. The issues I am facing now is how to include the operator "<<<" in the variable. – Odd Kviteberg Jun 29 '21 at 19:53
  • @OddKviteberg, once again, you _can't_, intentionally; if you could do that, bash would be completely unsuited to writing code that handles untrusted data. If you want to store syntax under a name, that's what functions are for. – Charles Duffy Jun 29 '21 at 19:54
  • @OddKviteberg, ...mind, you can certainly can write `with_sftp_script() { "$@" <<<$'put afile.txt\nexit\n'; }`, and then call `with_sftp_script sftp host:dir` -- but function call syntax is what it is, which is to say, the function needs to be in head position as the command to be run at invocation time. – Charles Duffy Jun 29 '21 at 19:57
  • @OddKviteberg, ...so the only way you're going to make `sftp host:dir $file` look like it works is by changing it to `eval "sftp host:dir $file"` -- and as described in the BashFAQ #48 link above, that opens up a whole host of security risks / potential bugs. If you have a good reason for what you're asking for, _expand your question to describe that good reason_ so we can try to find a solution that satisfies the underlying constraints. – Charles Duffy Jun 29 '21 at 20:04
  • `stfp host:dir "$file"` would work fine with [eval](https://stackoverflow.com/q/17529220/3776858). – Cyrus Jun 29 '21 at 20:06
  • @Cyrus, for a very risky value of "fine", one that includes letting malicious filenames or herestrings execute arbitrary local code. And BTW, I think I point that out in the comment directly above yours (but with more caveats -- caveats I'd argue it's irresponsible to leave out of discussion).. – Charles Duffy Jun 29 '21 at 20:06
  • @Cyrus, ...btw, the decision to write `eval "sftp host:dir $file"` instead of `eval sftp host:dir "$file"` is a very deliberate one -- doing that makes it clear to readers that `eval` _combines its entire argument list into a single string_ before parsing that string. Otherwise, consequences that follow from that processing tend to get forgotten -- hence people being surprised (as they often are) that `eval printf ' - %s\n' $something` and `prinf ' - %s\n' $something` behave nothing at all like each other, even _before_ `$something` is reached. – Charles Duffy Jun 29 '21 at 20:12

1 Answers1

2

Unquoted here-docs do interpolate. Hence,

sftp host:dir <<EOF
put ${file@Q}
exit
EOF

I have put quotes around $file to catch the case that the filename contains spaces.

UPDATE: Incorporating the comment by Léa Gris. Note: The syntax @Q is described in the bash man page under the heading Parameter transformation :

The expansion is a string that is the value of parameter quoted in a format that can be reused as input.

While it means that quoting is done in a way that the input can be reused by bash, IMO it should be sufficient for the syntax in a ftp script as well.

user1934428
  • 19,864
  • 7
  • 42
  • 87
  • `'$file'` will break when file name contains single quotes. With Bash 4+ you can expand variable with `${file@Q}` and it will automatically provide proper quoting if needed. – Léa Gris Jun 30 '21 at 07:31
  • @LeaGris: That it will break is what I wrote - but thank you for pointing out the idea with `@Q`. I didn't know this and will update my answer. – user1934428 Jun 30 '21 at 07:39
  • Don't forget to remove the quotes, as it will clash with the `${file@Q}` – Léa Gris Jun 30 '21 at 12:56