15

I would like to know if a certain systemd unit exists.

This should work for:

  • any type of unit (service, target, mount, ...)
  • running, disabled or masked unit

I know I could do this:

systemctl list-unit-files | grep "^my.target"

But it feels like there has to be a better way.

Optionally, I would like to be able to run this check by just specifying my without the need to specify ".service" (like for other systemctl commands), something like

systemctl exists my
Chris Maes
  • 3,282
  • 3
  • 21
  • 32

2 Answers2

7

I'm not aware of a native systemd way to do it, but you could (ab)use systemctl list-unit-files:

systemctl-exists() {
  [ $(systemctl list-unit-files "${1}*" | wc -l) -gt 3 ]
}

This creates a "testing" function that you could use like this:

systemctl-exists my && echo my exists as a systemd unit

The * suffix is there to allow systemd to match the given argument with any "type" (service, target, or mount). This function is hard-coded to the current systemctl list-unit-files output that includes at least three lines of output (when no matching units exist); more when there are matching units:

1. UNIT FILE            STATE
   (one or more matching unit files)
2. (a blank line)
3. "%d unit files listed."

Also note that the wildcard at the end could lead to false-positives if you have unit files with similar prefixes -- searching for "au" will find fool's gold with "auditd", "autofs", and others, even if you only expected the real thing "au.service". Spell out more of the service name if you know it: systemctl-exists au.service will do the right thing.

I initially thought systemctl cat would work as a filter, but it apparently assumes that the argument is a service and so does not filter appropriately for other types (e.g. target or mount).

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
  • thanks for the answer, but I fail to see much added value to my answer. Strange that such an option does not exist. – Chris Maes Sep 05 '19 at 15:47
  • 1
    There is *at least* one person who visits U&L that's more familiar with systemd than I am; if filbranden happens to see the question, I hope they can weigh in. – Jeff Schaller Sep 05 '19 at 15:48
  • 5
    `systemctl cat` works fine - systemctl itself tends to treat a unit without extension as having `.service`. Compare, e.g., `systemctl cat -- foo.service &> /dev/null && echo $?` and `systemctl cat -- -.mount &> /dev/null && echo $?` – muru Sep 05 '19 at 15:56
  • @muru, my concern was in trying to use `systemctl cat dev-mqueue`, for example, and have it find `dev-mqueue.mount`. Adding a wildcard doesn't help, and so that falls down on the "without the need to specify .service" requirement. – Jeff Schaller Sep 05 '19 at 16:01
  • But it also says, "like other systemctl commands", and in this respect it is behaving exactly like those commands. (That's to say, if `cat` failed, so would those.) – muru Sep 05 '19 at 16:02
  • @muru but it's not like the `list-unit-files | grep`, which picks up "any type of unit (service, target, mount, ...)" – Jeff Schaller Sep 05 '19 at 16:03
  • `systemctl cat` seems to search/find service units without specifying their type suffix. At least I can use it like that. But I'm already happy with providing that suffix in my case. – ygoe Feb 08 '21 at 21:53
  • If you want it to work with the whole name and any type (eliminate false positives) then just add a period before the wildcard: `systemctl list-unit-files "${1}.*"` – Compholio Oct 05 '21 at 15:58
1

Updated answer

Apparently, systemctl list-unit-files "$systemd_unit_name", will return exit status 0 if at least one unit* matching "$systemd_unit_name" exists, otherwise will return exit status 1.

* Note: systemctl list-unit-files ... apparently does not list device units (.device).

Updated oneliners

These (updated) oneliners can test unit existence for any type of unit, including service units, target units, socket units, as well as template units (e.g. [email protected]) and template instance units (e.g. [email protected]).

### exact non-device unit existence test
### test if some unit (not including device units) named 'foo.service' exists:

systemctl list-unit-files foo.service &>/dev/null && echo "this unit exists" || echo "this unit does not exist"


### pattern match non-devixe unit existence test
### test if at least one unit (not including device units) with a name matching 'ssh*' exists:

systemctl list-unit-files ssh* &>/dev/null && echo "at least one unit matches" || echo "no unit matches"

For device units, the systemctl status command is used instead . Despite systemctl's documentation mentioning that an exit status within 0 and 3 indicates a unit exists, and an exit status 4 indicates "no such unit", I noticed that for names ending on .device in particular, systemctl status doesntexist.device still returns exit status 3. Therefore, the following device unit-specific test considers exit status 0 as an indication that the specified device unit exists:

### exact device unit existence test
# test if some device unit named 'sys-module-fuse.device' exists:

systemctl status sys-module-fuse.device &>/dev/null && echo "this device unit exists" || echo "this device unit does not exist"

Flawed answer

(At least with systemd version 247, this previous version of this answer will give unreliable results when testing a template unit or template unit instance (i.e., unit name as an @, [1] [2]))

If systemctl's exit status is 4, then the specified unit name is unknown to systemd (i.e., the requested unit does not exist).

Flawed oneliner

# example oneliner to test on the existence of some unit named 'foo.service':
# !!! DOES NO WORK RELIABLY FOR TEMPLATE UNITS OR TEMPLATE UNIT INSTANCES

systemctl status foo.service &>/dev/null; if [[ $? == 4 ]]; then echo "this unit does not exist"; else echo "this unit exists"; fi

Flawed Bash script

#!/bin/bash
set -euo pipefail

# file test-systemd-unit-existence.sh
#
# this script tests if there exists a systemd system unit under the provided name.
#
# !!! DOES NO WORK RELIABLY FOR TEMPLATE UNITS OR TEMPLATE UNIT INSTANCES
#
# if it exists, this script echoes "'$SYSTEMD_UNIT' exists under the full unit name '$SYSTEMD_UNIT_FULL_NAME'",
# otherwise ("unit unknown"/"no such unit") echoes "'$SYSTEMD_UNIT' does not exist".
#
# the test is accomplished by executing "systemctl status $SYSTEMD_UNIT",
# then checking its exit status
#
# see https://www.freedesktop.org/software/systemd/man/systemctl.html#Exit%20status
#
# usage examples:
# ./test-systemd-unit-existence.sh ssh.service
# ./test-systemd-unit-existence.sh ssh
# ./test-systemd-unit-existence.sh basic.target
# ./test-systemd-unit-existence.sh doesntexist.service
# ./test-systemd-unit-existence.sh uuidd
# ./test-systemd-unit-existence.sh uuidd.service
# ./test-systemd-unit-existence.sh uuidd.socket
# ./test-systemd-unit-existence.sh uuidd.target

SYSTEMD_UNIT="$1"

# using "&>/dev/null" to discard stdout and stderr output ("--quiet" only discards stdout).
# due to "set -e", using " ... && true" construct to avoid script from
# exiting immediately on expectable nonzero exit code.
#
#   "[...] The shell does not exit if the command that fails is
#    [...] part of any command executed in a && or || list
#    [as long as it isn't the final command in this list]"
#
# see https://www.gnu.org/software/bash/manual/html_node/Lists.html
# see "-e" at https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html

systemctl status "$SYSTEMD_UNIT" &>/dev/null && true
SYSTEMCTL_EXIT_STATUS="$?"

if [[ "$SYSTEMCTL_EXIT_STATUS" == 4 ]]; then
  echo "'${SYSTEMD_UNIT}' does not exist"
else
  SYSTEMD_UNIT_FULL_NAME="$(systemctl show ${SYSTEMD_UNIT} --property=Id --value)"
  echo "'${SYSTEMD_UNIT}' exists under the full unit name '${SYSTEMD_UNIT_FULL_NAME}'"
fi
Abdull
  • 665
  • 1
  • 7
  • 13