1

I am trying to put following in a bash script:

ssh hostname "rm -rf ~/directory"

The above run successfully.

When I put it in a script like following, it does works:

#!bash

SSH_HOST=hostname
DIR_REMOTE='~/directory'

ssh $SSH_HOST "rm -rf $DIR_REMOTE"

However, when I try to encapsulate the whole command in a variable, it does not work:

#!bash

SSH_HOST=hostname
DIR_REMOTE='~/directory'

CMD="ssh $SSH_HOST \"rm -rf $DIR_REMOTE\""
echo "$CMD"
$CMD

It gives following error:

zsh:1: no such file or directory: rm -rf ~/directory

What am I doing wrong and how to fix?

John Siu
  • 5,056
  • 2
  • 26
  • 47
  • 1
    Storing commands in variables is almost always a bad idea; the way they're expanded is weird. Store executable code in functions, not variables (or don't store it at all, just execute it). See [BashFAQ #50: "I'm trying to put a command in a variable, but the complex cases always fail!"](http://mywiki.wooledge.org/BashFAQ/050), ["Why does shell ignore quoting characters in arguments passed to it through variables?"](https://stackoverflow.com/questions/12136948/why-does-shell-ignore-quoting-characters-in-arguments-passed-to-it-through-varia), and the many other questions linked from that one. – Gordon Davisson Jul 02 '23 at 05:16
  • You do not use `bash` but `zsh`. – Cyrus Jul 02 '23 at 07:17
  • 2
    Please paste your script at [shellcheck.net](http://www.shellcheck.net/). – Cyrus Jul 02 '23 at 07:38
  • Likely duplicate of https://stackoverflow.com/questions/18502945/how-to-execute-a-remote-command-over-ssh-with-arguments – Léa Gris Jul 02 '23 at 09:28
  • @Cyrus `zsh` is the remote host shell, my script is `bash`. – John Siu Jul 02 '23 at 10:53
  • @LéaGris Similar, but not the same, actually the solution is opposite. – John Siu Jul 02 '23 at 10:54

1 Answers1

1

In the first version of the script, your local bash interpreter is removing the quotes before ssh is run, so

ssh $SSH_HOST "rm -rf $DIR_REMOTE"

and

ssh $SSH_HOST rm -rf $DIR_REMOTE

do exactly the same thing -- you're passing rm -rf ~/directory to $SSH_HOST via ssh.

When you run

CMD="ssh $SSH_HOST \"rm -rf $DIR_REMOTE\""
echo "$CMD"
$CMD

the bash interpreter is removing the first set of quotes, and you're passing the quoted string "rm -rf ~/directory" to $SSH_HOST via ssh. The remote shell (in this case zsh) is trying to execute the file "rm -rf ~/directory", and cannot, so it's throwing an error message.

If you leave off the inner quotes:

CMD="ssh $SSH_HOST rm -rf $DIR_REMOTE"
echo "$CMD"
$CMD

You will be sending the unquoted string rm -rf ~/directory via ssh, and zsh will interpret it correctly.


Edit: As mentioned in the comments, running code from variables, while possible, is brittle and likely to be insecure; as such, it's considered bad practice. Variables are for data, functions are for code.

As such, you're much better off writing a function:

#!/bin/bash

remove_remote_dir() {
    local SSH_HOST=$1
    local DIR_REMOTE=$2

    ssh $SSH_HOST "rm -rf $DIR_REMOTE"
}

remove_remote_dir hostname '~/directory'
Barton Chittenden
  • 4,238
  • 2
  • 27
  • 46
  • While your answer may work for this special case; it does not help @johnsiu learn better practices with placing code in a function. Also expanding `$DIR_REMOTE` as-is without properly escaping and securing its content; is creating a remote code execution vulnerability. – Léa Gris Jul 02 '23 at 08:48
  • @LéaGris I know doing it this way is a mess. But this is for a small script from my own use, so the risk is minimum. – John Siu Jul 02 '23 at 10:56