7

I have a set of scripts that I use to download files via FTP and then delete them from the server.

It works as follows:

for dir in `ls /volume1/auto_downloads/sync-complete`
do
if [ "x$dir" != *"x"* ]
then
echo "DIR: $dir"

echo "Moving out of complete"
        # Soft delete from server so they don't get downloaded again
        ssh [email protected] mv -v "'/home/dan/Downloads/complete/$dir'" /home/dan/Downloads/downloaded

Now $dir could be "This is a file" which works fine.

The problem I'm having is with special characters eg:

  • "This is (a) file"
  • This is a file & stuff"

tend to error:

bash: -c: line 0: syntax error near unexpected token `('
bash: -c: line 0: `mv -v '/home/dan/Downloads/complete/This is (a) file' /home/dan/Downloads/downloaded'

I can't work out how to escape it so both the variable gets evaluated and the command gets escaped properly. I've tried various combinations of escape characters, literal quotes, normal quotes, etc

3
  • 2
    How about rsync --remove-source-files serverfault.com/a/363925/69736 . Much easier! Commented Jun 2, 2013 at 21:39
  • It's not clear how dir is being set to the single string 'This is (a) file. It should be set successively to 4 separate strings 'This', 'is', '(a)', and 'file'. Don't parse the output of ls`; switch the directory and iterate over a glob (see my answer for details). Commented Jun 3, 2013 at 13:25
  • [ "x$dir" != *"x"* ] -- this only works for pattern matching in [[, not [ Commented Jun 15, 2020 at 15:28

5 Answers 5

22

If both sides are using bash, you can escape the arguments using printf '%q ', eg:

ssh [email protected] "$(printf '%q ' mv -v "/home/dan/Downloads/complete/$dir" /home/dan/Downloads/downloaded)"
Sign up to request clarification or add additional context in comments.

4 Comments

The printf is only executed on the local end, so bash is not needed on the remote host.
%q escapes according to Bash's escaping rules. If bash is not the remote shell, there is no guarantee that the escapes are valid.
...might also consider using printf -v cmd_q '%q ' ..., and then ssh user@host "$cmd_q", avoiding paying a subshell penalty altogether.
BTW, in modern bash (5.x certainly, maybe late 4.x), one can ssh dan@host "mv -v /path/to/${dir@Q} /path/to/Downloads
2

You need to quote the whole expression ssh user@host "command":

ssh [email protected] "mv -v /home/dan/Downloads/complete/$dir /home/dan/Downloads/downloaded"

4 Comments

That resulted in the variable being evaluated on the remote end, not the local
Oh, I see. So I update my answer without the $ escaped. It should work now.
Ah now I see you had an echo before the ssh command. So what's better doing is to wrap this inside a $(), as indicated by @Will Palmer answer.
The command will fail if the $dir value contains a whitespace.
0

I'm confused, because your code as written works for me:

> dir='foo & bar (and) baz'
> ssh host  mv -v "'/home/dan/Downloads/complete/$dir'" /home/dan/Downloads/downloaded
mv: cannot stat `/home/dan/Downloads/complete/foo & bar (and) baz': No such file or directory

For debugging, use set -vx at the top of the script to see what's going on.

4 Comments

try with dir="nathan's crew.txt"
@Will, you got me on that one, so your answer is definitely better than mine, but I'm still confused as to why the OP had the problems he stated with the script he posted.
The actual problem comes from the way remote commands are passed in over SSH: All arguments are joined together and passed in to the remote shell as a single string- so those double-quotes in the original post's example are actually only there to make the single-quotes exist on the remote end.
@Will, yes, but unless $dir contains a single-quote (or possibly an exclamation point) it should work. The OP said it was getting tripped up on parentheses.
0

Will Palmer's suggestion of using printf is great but I think it makes more sense to put the literal parts in printf's format.

That way, multi-command one-liners are more intuitive to write:

ssh user@host "$(printf 'mkdir -p -- %q && cd -- "$_" && tar -zx' "$DIR")"

Comments

-1

One can use python shlex.quote(s) to

Return a shell-escaped version of the string s

docs

1 Comment

This has an advantage over the printf %q approach insofar as the strings it returns are guaranteed to work on all POSIX-compliant shells, not just bash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.