11

If I run this command

/bin/bash -c 'while true;do /usr/bin/etcdctl set my-container "{\"host\": \"1\", \"port\": $(/usr/bin/docker port my-container 5000 | cut -d":" -f2)}" --ttl 60;sleep 45;done'

I get back from etcd what I expect {"host":"1", "port":49155}

But if I put it in a systemd file

ExecStart=/bin/bash -c 'while true;do /usr/bin/etcdctl set my-container "{\"host\": \"1\", \"port\": $(/usr/bin/docker port my-container 5000 | cut -d":" -f2)}" --ttl 60;sleep 45;done'

I get back {host:1, port:49155}

Any idea of why the escaping is different inside of the file? How can I fix it? Thanks!!

MParker
  • 315
  • 2
  • 3
  • 10

3 Answers3

14
systemd-escape '\"a fun thing"\' 

output: \x5c\x22a\x20fun\x20thing\x22\x5c

[Service]
ExecStart=/bin/sh -c 'echo "\x5c\x22a\x20fun\x20thing\x22\x5c"'

will print a fun thing

mnille
  • 1,328
  • 4
  • 16
  • 20
user6588075
  • 141
  • 1
  • 2
  • 1
    The OP's problem is with being able to emit literal quotes from `echo`. That is to say, they want to know how to print `"a fun thing"`, not `a fun thing`. – Charles Duffy Aug 24 '17 at 17:07
7

Systemd is doing isn't like bash as you now know, hence the escaping problem. In fact, systemd removes single and double quotes after parsing them. That fact is right out of the documentation (I went thru this too, then read :D).

The solution, call a script that echo back that info you need (with escaped quotes) if your purpose allows that.

MiiinimalLogic
  • 820
  • 6
  • 11
  • 1
    I don't get it, please take a look at this unit file https://gist.github.com/digital-wonderland/e0bd8e0d4c91a7fec2c7#file-elasticsearch-service ... It's full of double qoutes and it couldn't even work without them, right? – lisak Sep 01 '15 at 08:53
  • 3
    This is untrue. I mean, yes, systemd *does* remove quotes, but it does so in the same sense in which the shell removes quotes (and yes, normal shell parsing *does* have a step called "quote removal"), in that it removes literal quotes *after using them to guide string-splitting*. `ExecStart=/usr/bin/docker run something/else -- -c "a fun thing"` is **absolutely not** identical in behavior to `ExecStart=/usr/bin/docker run something/else -- -c a fun thing` – Charles Duffy Aug 23 '17 at 22:08
  • (Yes, it *shows* it in the logs in a way that doesn't represent the actual divisions between argv elements, but if you look at `/proc/$pid/cmdline` for the invoked service, you'll see NULs where the boundaries properly belong). – Charles Duffy Aug 23 '17 at 22:13
  • I'm not even sure when the edit by naftuli Kay happened but i don't think I added that part @CharlesDuffy – MiiinimalLogic Aug 23 '17 at 22:32
  • Oh, **wow**. That edit by @NaftuliKay really should have been a separate answer. – Charles Duffy Aug 23 '17 at 22:33
  • @Naftuli Kay what did you add to my answer? – MiiinimalLogic Aug 23 '17 at 22:33
6

In short -- it's different because systemd does its own string-splitting, unescaping and expansion, and the logic it uses isn't POSIX-compliant.

You can still do what you want, but you'll need more backslashes:

ExecStart=/bin/bash -c '\
  while :; do \
    port=$(/usr/bin/docker port my-container 5000 | cut -d: -f2); \
    /usr/bin/etcdctl set my-container "{\\\"host\\\": \\\"1\\\", \\\"port\\\": $port}" --ttl 60; \
    sleep 45; \
  done'

Note the use of \\\" for every literal " character in the desired output.


By the way -- personally, I advise against trying to generate JSON through string concatenation -- it's prone to injection vulnerabilities (if someone could put content of their choice in the output of the docker port command, they could potentially insert other key/value pairs into your data by having , "evil": true be in the port variable). This class of issues is avoided by using jq:

ExecStart=/bin/bash -c '\
  while :; do \
    port=$(/usr/bin/docker port my-container 5000 | cut -d: -f2); \
    json=$(jq -nc \
      --arg host 1 \
      --arg port "$port" \
      '{} | .host=$host | .port=($port | tonumber)'); \
    /usr/bin/etcdctl set my-container "$json" --ttl 60; \
    sleep 45; \
  done'

As a happy side effect, the above avoids needing any literal double-quote characters (the only ones used are syntactic to the copy of sh), so we don't need any backslashes to be passed through from systemd to the shell.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441