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?

Gerald Schneider
  • 681
  • 9
  • 20
  • 2
    Well that linked post doesn't really explicitly say what the correct solution in the case here is, but you can just run `ssh somehost 'test -f ... && echo yes || echo no'` directly, without an intervening shell. That is unless you need it because you want to run the script on a different shell than the login shell of the remote user. Then you need an additional set of quotes. – ilkkachu Jun 14 '22 at 06:46
  • Right. On the remote side your `-f` becomes an argument to `bash`, not to `test`. If your local shell is Bash then you may find this helpful: [*How can I single-quote or escape the whole command line in Bash conveniently?*](https://superuser.com/q/1531395/432690) – Kamil Maciorowski Jun 14 '22 at 06:51
  • Thank you @ilkkachu, I was still going to the answers of the linked post, but I also got the impression that while it definitely is the same problem, it doesn't really answer my question. But your comment solves it. – Gerald Schneider Jun 14 '22 at 06:52
  • @GeraldSchneider, ok, I was writing that answer anyway when the question was marked a duplicate – ilkkachu Jun 14 '22 at 06:56
  • This question isn't actually asking for a solution, just the reason why, which Gilles's answer does explain fairly well, so I'd say it does really answer this question too. – muru Jun 14 '22 at 06:59
  • 1
    @muru, hehe I guess strictly speaking you're correct, just that getting the "what instead" in addition to the "why not this" can be rather helpful too, esp. when one isn't that familiar with the subject. (Anyway, Gilles' answer has the gist of it, so I didn't want to override the close by myself right away.) – ilkkachu Jun 14 '22 at 07:06
  • Related: [Executing \`sh -c\` script through SSH (passing arguments safely and sanely)](https://unix.stackexchange.com/q/450020) – Kusalananda Jun 14 '22 at 08:07

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
  • 133,243
  • 15
  • 236
  • 397