How does this variable escaping work in a systemd unit file?

5,754

Solution 1

This is documented in systemd.service(1). ${PORT} is expanded by systemd. To pass the $ to the shell you need to write $$, so $${PORT}. The important line is this:

To pass a literal dollar sign, use "$$". Variables whose value is not known at expansion time are treated as empty strings.

Solution 2

  1. if the content of PORT comes from some other bash variable you would be dealing with an indirect reference then please try:

    ${!PORT}
    
  2. I assume you are sure your shell is Bash

Share:
5,754

Related videos on Youtube

Daniel Buckmaster
Author by

Daniel Buckmaster

Startup founder, renewables advocate, reluctant technophile.

Updated on September 18, 2022

Comments

  • Daniel Buckmaster
    Daniel Buckmaster over 1 year

    I have a fairly simple unit file for a discovery sidekick service for a server instance I'm running on CoreOS. The unit file looks like this:

    [Unit]
    Description=Discovery for frontend server (instance %i)
    BindsTo=frontend@%i.service
    After=frontend@%i.service
    
    [Service]
    EnvironmentFile=/etc/environment
    ExecStart=/usr/bin/bash -c ' \
        while true; do \
            export PORT=$(docker port frontend%i 80 | sed s/.*://); \
            etcdctl set /services/frontend/%i "${COREOS_PRIVATE_IPV4}:$PORT" --ttl 60; \
            sleep 45; \
        done'
    ExecStop=/usr/bin/etcdctl rm /services/frontend/%i
    
    [X-Fleet]
    MachineOf=frontend@%i.service
    

    This works fine, but it took me ages to get to this stage, because if I change the etcdctl line to this:

    etcdctl set /services/frontend/%i "${COREOS_PRIVATE_IPV4}:${PORT}" --ttl 60; \
    

    Then it doesn't work - it ends up setting a value like 100.45.218.3:, with no port. Along the way I spent a lot of time playing with different uses of the $PORT variable, and I have no idea why the configuration I settled on works. At one point I had this in the script:

    echo hi $PORT; \
    echo "hi $PORT"; \
    echo hi ${PORT}; \
    echo "hi ${PORT}"; \
    

    And got journal logs like this:

    Aug 17 01:05:07 core-01 bash[53694]: hi 32769
    Aug 17 01:05:07 core-01 bash[53694]: hi 32769
    Aug 17 01:05:07 core-01 bash[53694]: hi
    Aug 17 01:05:07 core-01 bash[53694]: hi
    

    Essentially my question is: what's going on here? This flies in the face of how I understand {} to work in bash scripts. And why can I use curlies on the COREOS_PRIVATE_IPV4 variable (which is exported from /etc/environment, but not for PORT?

  • Daniel Buckmaster
    Daniel Buckmaster over 8 years
    Thanks for the response! 1. PORT comes from a line in the script export PORT=$(docker ...); 2. CoreOS ships with bash 4.2
  • Pat
    Pat over 8 years
    did you try ${!PORT} in your script??
  • Daniel Buckmaster
    Daniel Buckmaster over 8 years
    I did, and it seems to give the same result (an empty string).
  • Daniel Buckmaster
    Daniel Buckmaster over 8 years
    Thanks for that! That makes sense now, I didn't notice that variables could be substituted by systemd differently to during execution of the script itself...