Bash – Why do ls parameters seem to not work in commands run via ssh host bash -c

bashsshwildcards

I have a local and a remote host, both running Ubuntu, with the shell set to bash. In my home directory, I have two files, file-1 and file-2, both on the local host, and on a remote host called remote. There are some other files in each home directory, and I want to list only files matching file-*.

Locally, these produce the expected result, file-1 file-2:

$ ls file-*
$ bash -c 'ls file-*'

But these commands return ALL files in my home directory on the remote. What's going on there?

$ ssh remote bash -c 'ls file-*'
$ ssh remote bash -c 'ls "file-*"'
$ ssh remote bash -c 'ls file-\*'
$ ssh remote bash -c 'ls "file-\*"'

I know that simply ssh remote 'ls file-*' produces the expected result, but why does ssh remote bash -c 'ls ...' seem to drop the arguments passed to ls ...? (I've also piped the output from the remotely executed ls, and it's passed along, so only the ls seems to be affected: ssh remote bash -c 'ls file-* | xargs -I {} echo "Got this: {}"'.)

Best Answer

The command being executed on the remote host when you use ssh remote bash -c 'ls file-*' is

bash -c ls file-*

That means bash -c executes the script ls. As positional parameters, the bash -c script gets the names on the remote host matching file-* (the first of these names will be put into $0, so it's not really part of the positional parameters). The arguments won't be passed to the ls command, so all names in the directory are listed.

ssh passes the command on the the remote host for execution with one level of quotes removed (the outer set of quotes that you use on the command line). It is the shell that you invoke ssh from that removes these quotes, and ssh does not insert new quotes to separate the arguments to the remote command (as that may interfere with the quoting used by the command).

You can see this if you use ssh -v:

[...]
debug1: Sending command: bash -c ls file-*
[...]

The three other commands that you show works the same, but will only set $0 to the string file-* while not setting $1, $2, etc. for the bash -c shell.

What you may want to do is to quote the whole command:

ssh remote 'bash -c "ls file-*"'

Which, in the ssh -v debug output, gets reported as

[...]
debug1: Sending command: bash -c "ls file-*"
[...]

In short, you will have to ensure that the string that you pass as the remote command is the command you want to run after your local shell's quote removal.

You could also have used

ssh remote bash -c \"ls file-\*\"

or

ssh remote bash -c '"ls file-*"'
Related Question