3

I'm trying to reboot a number of Ubuntu servers via script when a reboot is necessary.

When I execute bash with the test as a noninteractive command I get the result that no reboot is required, even though the file /var/run/reboot-required exists.

usera@client:~$ ssh server02 bash -c 'test -f /var/run/reboot-required && echo sudo reboot || echo "$(hostname): no reboot required"'
server02: no reboot required

When I log on to the same server via SSH and run my test manually I get the correct result, sudo reboot.

usera@client:~$ ssh server02
Last login: Tue Jun 14 08:03:00 2022 from 146.140.16.1
usera@server02:~$ test -f /var/run/reboot-required && echo sudo reboot || echo "$(hostname): no reboot required"
sudo reboot
usera@server02:~$ bash -c 'test -f /var/run/reboot-required && echo sudo reboot || echo "$(hostname): no reboot required"'
sudo reboot

What do I need to change to get the correct result?

1 Answers1

7

I'm pretty sure that's because of the way SSH passes a command line to the remote end and it does that by concatenating all the arguments it gets, joining them with spaces and letting the shell on the remote parse and execute that.

Consider e.g. that ssh somehost ls -l /etc/passwd works the same as ssh somehost 'ls -l /etc/passwd', the latter doesn't give an error about a weirdly named command not existing, unlike it does if you just run 'ls -l /etc/passwd' directly on a shell command line.

So,

ssh somehost bash -c 'test whatever || echo no'

turns into the command line

bash -c test whatever || echo no

where Bash gets to run the command test, with $0 set to whatever. test with no args fails, so the || echo runs. You can try something like ssh somehost bash -c 'ls -l /etc/passwd' too, it should just run ls in your remote home directory.

So, try without the intermediate shell:

ssh server02 'test -f /etc/passwd && echo passwd exists || echo "$(hostname): passwd does not exist"'

or you could do something like

ssh server02 'bash -c "test -f /etc/passwd && echo yes || echo \"\$(hostname): no\"'

but the quoting there does get icky if you want to nest another quoted string and make sure it's the innermost shell that runs any expansions you have. It doesn't matter that much with $(hostname) since it'll give the same result from either shell on the remote, but in a more general case the quoting hell is there. In complex cases like that, it'd be far easier to just create a file on the remote and run the script from there.

This is basically the same issue as in ssh command with quotes.

See also Executing `sh -c` script through SSH (passing arguments safely and sanely) for other solutions for passing complex commands, and How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user? for the rarer case involving a possibly unknown remote login shell.

ilkkachu
  • 138,973