Why does `cd` command not work via SSH?

ssh doesn't let you specify a command precisely, as you have done, as a series of arguments to be passed to execvp on the remote host. Instead it concatenates all the arguments into a string and runs them through a remote shell. This stands out as a major design flaw in ssh in my opinion... it's a well-behaved unix tool in most ways, but when it comes time to specify a command it chose to use a single monolithic string instead of an argv, like it was designed for MSDOS or something!

Since ssh will pass your command as a single string to sh -c, you don't need to provide your own sh -c. When you do, the result is

sh -c '/bin/sh -c cd /boot && ls -l'

with the original quoting lost. So the commands separated by the && are:

`/bin/sh -c cd /boot`
`ls -l`

The first of those runs a shell with the command text "cd" and $0="boot". The "cd" command completes successfully, the $0 is irrelevant, and the /bin/sh -c indicates success, then the ls -l happens.


İt's a quoting issue. Ssh already runs the command you pass it in a shell. When you pass multiple parameters, they are concatenated with a space in between to build a string. So the remote command that you are running remotely is /bin/sh -c cd /boot && ls -l (no quotes, because the quotes in your command were interpreted by the local shell).

/bin/sh -c cd /boot runs /bin/sh and tells it to run the command cd and also to set $0 to /boot. Once this is done, the parent shell (the one launched by sshd) runs ls -l.

In your case, just remove the sh -c which is completely useless unless your remote shell (as indicated in /etc/passwd or other password database) does not understand this command.

ssh root@server "cd /boot && ls -l"

If you need to invoke a different shell, you must quote the remote command to protect it from expansion by the remote shell invoked by sshd. For example, if your login shell is dash and you want to run a bash command:

ssh root@server 'bash -c "cd ~bob && ls -l"'

I think it has more to do with how the options are getting parsed by the shell. For example, this works:

$ ssh root@server /bin/sh -c '"cd /boot && ls -l"'

This has the same issue as your command:

$ ssh root@server /bin/sh -c 'cd /boot && ls -l'

If you enable the -v switch to ssh you can see what's going on:

1st command:

debug1: Sending command: /bin/sh -c "cd /boot && ls -l"

2nd command:

debug1: Sending command: /bin/sh -c cd /boot && ls -l

Typically when sending commands through ssh you have to pay special attention to the quoting and wrap quotes within quotes as the various layers strip them away. Also don't bother sending /bin/sh.

You can do very useful thing once you understand the quoting of ssh such as the following. This will run the command on the remote server but collect the results in a file locally on the system where you ran the ssh command:

$ ssh root@server 'free -m' > /tmp/memory.status

or this, where you tar a directory on a remote server and create it on the local system:

$ ssh remotehost 'tar zcvf - SOURCEDIR' | cat > DESTFILE.tar.gz

References

  • Pushing & Pulling Files Around Using tar, ssh, scp, & rsync