0

I have an obvious problem with quoting in bash such that I couldn't really solve for a while. Here is a loop with executing a remote command in a sudo-subshell on a remote server via ssh:

for HOST in $RUNNER_LIST; do
  ssh -t $HOST "sudo bash -c \"sed -i '$a 10.10.40.29 myhost' /etc/hosts\""
done 2>/dev/null

Now, this simple approach, of course, doesn't work. That $a gets expanded by the local shell, and since that's an unset variable sed executes the expression ' 10.10.40.29 myhost'

I've tried out several approaches, and during writing this question, I somehow found out that this worked:

ssh -t $HOST "sudo bash -c \"sed -i '"'\$'"a 10.10.40.29 myhost' /etc/hosts\""

and using prepended set -x, I see that on the remote shell, it's expanded to

sudo bash -c 'sed '\''$a 10.10.40.29 myhost'\'' /etc/hosts'

I was surprised since my intention was to escape $. Instead of it, the quotes got escaped. So I would like to have a clear explanation of how this expansion worked. And maybe there is a way to make this command more readable?

Danny Lo
  • 202
  • 4
  • 13
  • 1
    (1) What is the reason for `bash -c`? Why not `sudo sed …`? (2) [How can I single-quote or escape the whole command line in Bash conveniently?](https://superuser.com/q/1531395/432690) – Kamil Maciorowski Feb 28 '22 at 22:01
  • I use `bash -c` in this loop because usually I want to execute a command group as root, for instance `bash -c "yum update -y && reboot"` – Danny Lo Mar 01 '22 at 11:04
  • 1
    For this case can add a space `sed '$ a blah'` because shell won't treat that as a variable but sed will treat it as a valid command. Or can do `echo 10.1.2.3 name | sudo tee -a /etc/hosts` which doesn't need any `$` _or_ internal quoting. – dave_thompson_085 Mar 05 '22 at 09:57

1 Answers1

0

This does not really answer your question (I did read your post too quickly, sorry); To properly do what you wanted, you need to doubly escape the $ (for sed, and for bash) :

for HOST in $RUNNER_LIST; do
  ssh -t $HOST "sudo bash -c \"sed -i '\\\$a 10.10.40.29 myhost' /tmp/hosts\""
done 2>/dev/null

Without the bash -c, only one level is necessary :

for HOST in $RUNNER_LIST; do
  ssh -t $HOST "sudo sed -i '\$a 10.10.40.29 myhost' /tmp/hosts"
done 2>/dev/null
Bruno
  • 140
  • 6