bash test wrong result when run noninteractively
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
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,
usera@client:~$ ssh server02 Last login: Tue Jun 14 08:03:00 2022 from 220.127.116.11 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?
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.
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
$0 set to
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.