0

I am trying to execute commands via ssh remotely. But I have the calling command saved as variable such as:

SSH_CMD="ssh -i path_to/identity"
SSH_USER=user
SSH_DST=server
dir1=dir1
dir2=dir2

now if I

eval $SSH_CMD $SSH_USER@$SSH_DST "mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"

I get errors (they are typically: mv: cannot stat 'dir1': No such file or directory)

but if I:

ssh -i path_to/identity $SSH_USER@$SSH_DST "mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"

That works. So the problem seems in how I am handling eval. What is the correct way?

atapaka
  • 541

2 Answers2

3
eval $SSH_CMD $SSH_USER@$SSH_DST "mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"

This is practically the same as running

eval "$SSH_CMD $SSH_USER@$SSH_DST mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"

eval joins the arguments it gets to a single string, and then runs that as shell command. In particular, the && acts on the local side, so the shell runs first ssh ... mkdir dir1, and if that succeeds, it runs mv dir1 dir2, etc. mkdir runs on the remote host, but mv on the local host, where dir1 doesn't exist.


There's no need to use eval here. Your first command would do without it:

$SSH_CMD $SSH_USER@$SSH_DST "mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"

When $SSH_CMD is not quoted, it gets split to the three words ssh, -i, and path_to/identity just as you want. This works, but is somewhat unclean, and you'd get into trouble if you had to include arguments with whitespace within $SSH_CMD, or used glob characters there.

If you need to store a variable number of command arguments, like in SSH_CMD, it's better to use an array. Also, quote the other variables to prevent word splitting, too:

ssh_cmd=(ssh -i path_to/identity)
ssh_user=user
ssh_host=server
dir1=dir1
dir2=dir2
"${ssh_cmd[@]}" "$ssh_user@$ssh_host" "mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"

See BashFAQ 050: I'm trying to put a command in a variable, but the complex cases always fail! and How can we run a command stored in a variable? for more details.

ilkkachu
  • 138,973
2

I see no reason to use eval here, it's simply introducing the potential for many vulnerabilities into your command.

You should store options in an array when possible instead of a variable like so:

SSH_OPTS=( -i path_to/identity )
SSH_USER=user
SSH_DST=server
dir1=dir1
dir2=dir2

Then you should ensure to always double quote your variables:

ssh "${SSH_OPTS[@]}" "${SSH_USER}@${SSH_DST}" "mkdir $dir1 && mv $dir1 $dir2 && touch $dir2"
jesse_b
  • 37,005
  • 1
    That last bit, if it was sourced or executed by a shell (it's not clear in the question) it would try to run -i as a command. And now it's updated in the question. – Kusalananda Feb 21 '19 at 18:13
  • My bad, the SSH_COMMAND is quoted, see the edit. I apologize. What would be the more general case if I wanted to keep the name ssh stored in a variable as well? – atapaka Feb 21 '19 at 18:14
  • @leosenko: The only reason I could think of to store the ssh command in a variable would be to specify the full path and ensure you are using a specific version of a command. However you can just add it into the beginning of the SSH_OPTS array if you desire. I would consider that obfuscated. – jesse_b Feb 21 '19 at 18:17