Need help using systemd timer/service instead of contab to run weekly check with clamav/clamdscan

I have been using clamdscan to run checks on selected directories successfully once every week for a wile using crontab but recently I wanted to move to systemd timer instead as mentioned in the wiki I used to set it up.

The date on the logs is really nice and I want to keep that, but here comes the problem.
Usually I am able to figure out escape characters and where I have to use them, but in this case, I just crash into a wall whatever I try.

The timer works perfectly but the service file reports “Ignoring unknown escape sequences” because, yeah, I cant figure out how to do them so the path to the logfile becomes totally whack whatever I try.

My service file WITHOUT any \ added, this is the line that works in cli and crontab (so its easier for you to see what the end result I want to achieve).

Description=Run clamdscan every Sunday at 18:00, started by timer

ExecStart=/usr/bin/clamdscan --fdpass --multiscan --move=/home/bedna/.clam/quarantine --log=/home/bedna/.clam/logs/$(date +\%Y-\%m-\%d).log /redacted/path1 /redacted/path2 /redacted/path3 /redacted/path4 2>/dev/null 1>&2
#ExecStop=/usr/bin/notify-send -u normal 'Clamdscan done' "Check ~/.clam/logs/${CLAMDSCAN_TIME}.log for more info"

The ExecStop is disabled at the moment, I was trying to make an environment variable by running ExacStartPre=/usr/bin/systemctl --user set-environment CLAMDSCAN_TIME=$(date +\%Y-\%m-\%d) but with little success, I simply cant figure out what characters to escape or not, it it the + that messes everything up?

Please help.

The service and timer are at --user level residing in ~/.config/systemd/user if that makes a difference.

This syntax is inspired by shell syntax, but only the meta-characters and expansions described in the following paragraphs are understood, and the expansion of variables is different. (systemd.service)

Write your own script file and let it execute in the ExecStart portion:

#!/usr/bin/env bash
set -euxo pipefail

DATE="$(date +%Y-%m-%d)"



"${CMD[@]" "${ARGS[@]}" 2> /dev/null 1>&1
1 Like

Yes, that was always an option, but not an answer to the topic. xD
Or is it simply not possible to get the string to work in a systemd service?
Is it because of the + or something because I use escape characters in other systemd services without problem.

If a scriptfile is the ONLY option I’d rather just stay with crontab.

All I need is a current date in the log name, if there are other ways to achieve this within a systemd service that would also be a solution.

Have you tried

ExecStart=/bin/bash -c '/usr/bin/clamdscan --fdpass --multiscan --move=/home/bedna/.clam/quarantine --log=/home/bedna/.clam/logs/$(date +%Y-%m-%d).log /redacted/path1 /redacted/path2 /redacted/path3 /redacted/path4 2>/dev/null 1>&2'
1 Like

Yeah, that was the first I did, but with " instead of '… hmmmm, now that you mention it…
oh you mean without the escape characters… I have not… give me a sec…

ExecStart=/usr/bin/clamdscan --fdpass --multiscan --move=/home/bedna/.clam/quarantine --log=/home/bedna/.clam/logs/$(date +%Y-%m-%d).log etc etc…
ERROR: Can't access file /home/bedna/+/home/bedna/.config/systemd/user-86d3f4ac45b448848750f60a2402f259-/run/user/1000/credentials/clamdscan.service).log
in journalctl and actually creates a logfile in correct location named “$(date”

I will try with /usr/bin/bash -c but pretty sure it will return exactly the same, it always has before.

Furhtermore, If I escape ONLY the first $ it starts, but the logfile gets very long in the string. it seems the other $ gets data, to translate into the user and the service or something, nothing makes sense to me.

ps aux | grep clamdscan
/usr/bin/clamdscan --fdpass --multiscan --move=/home/bedna/.clam/quarantine --log=/home/bedna/.clam/logs/$(date +/home/bedna/.config/systemd/user-86d3f4ac45b448848750f60a2402f259-/run/user/1000/credentials/clamdscan.service).log etc etc etc

The program just freezes and I have to kill -9 it.

Do you know the difference between ' and "? It is important to use the ' for the bash -c command.

Oh, and maybe you additionally need to escape the percentage sign with double percentage signs: %%

ExecStart=/bin/bash -c '/usr/bin/clamdscan --fdpass --multiscan --move=/home/bedna/.clam/quarantine --log=/home/bedna/.clam/logs/$(date +%%Y-%%m-%%d).log /redacted/path1 /redacted/path2 /redacted/path3 /redacted/path4 2>/dev/null 1>&2'

You might not like it, but it’s definitely a solution.

Yeah, the coin dropped the first time you mentioned it. In this case it actually works with both, my idea was to have a variable to use, but since I now don’t, the usage of ’ is good practice.

Yepp, that’s what did it.
Oddly enough % is not mentioned in the link you posted and that is the same manpage I use for systemd, that’s why I couldn’t figure it out… :open_mouth:

Final result:

Description=Schedule Clamdscan to run every Sunday at 18.00

OnCalendar=Sun *-*-* 18:00:00



Description=Run clamdscan every Sunday at 18:00, started by timer

ExecStart=/usr/bin/bash -c '/usr/bin/clamdscan --fdpass --multiscan --move=/home/bedna/.clam/quarantine --log=/home/bedna/.clam/logs/$(date +%%Y-%%m-%%d).log /redacted/path1/ /redacted/path2/ /redacted/path3/ /redacted/path4/ 2>/dev/null 1>&2'
ExecStop=/usr/bin/notify-send -u normal 'Clamdscan done' 'Check ~/.clam/logs/ for more info'

systemctl --user dameon-reload && systemctl --user enable --now clamdscan.timer

I THINK the notification will work after clamdscan is done, I guess I’ll know next Sunday. xD

Thank you and maybe someone else will find this useful, not sure I have privileges to add it to the wiki. xD

1 Like

Good for you .

You can simply start the service to test it.

systemctl --user start clamscan

Yeah… well… It takes about 3-4h to run the scan even if I use +20 cores… xD
It’s ok, the notification works, what I am unsure of is WHEN it will trigger or if at all.
Will it only run after ExecStart is done or will it not run because the script is never actually stopped. Maybe ExecStartPost (or end or what it now was, can’t remember) is what I should use.

I could probably find out pretty easy, I would just make up a dummy service and try but I’m to lazy. I can wait to find out, it’s just a notification. :smiley:

Also, you forgot to teach me about how I would need to add an [Install] target section if I want to run the service. Or can I start it anyway, now I’m unsure. :stuck_out_tongue_winking_eye:

Yeah, ExecStop is not the correct entry. It is for stopping the service.

You’re right and should use the ExecStartPost.

1 Like

The more I try to read about ExecStartPost the more confused I get, It’s not really mentioned in man pages, not sure it will actually work, maybe I’ll do some testing.

But 2 other ways should work since its nested with bash.

  • add && exit 0 and then add the notify-send to ExecStop
  • add && notify-send blahblahblah after in the same ExecStart as clamdscan.

&& exit 0 is redundant and I don’t know what it should accomplish.

The man page of systemd.service states:

ExecStartPost= commands are only run after the commands specified in ExecStart= have been invoked successfully

So, it is exactly what you’re looking for.

You don’t need to enable the service, so you don’t need an [Install] section.
The .timer file should have the section


so that the timer is started itslf.

You could test the ExecStartPost by setting ExecStart=/usr/bin/sleep 30 temporarily to simulate the “long” running process.

It would accomplish that the ExecStop would be triggered when clamdscan is done.

I have been looking everywhere for that man page, I simply cant find it. :open_mouth:
But I have seen similar explanations on other pages like stack exchange and such.
But it does NOT seem to be what I’m looking for, doesn’t “invoke” mean “started” in this case?
I want the notification when clamdscan is DONE.

I know, kinda what I said.
What I do not know though is if I can START it, or if it simply means I can’t enable it.

Yeeeeeah, that would need to be like 3-4HOURS then.
Oh ■■■■, my dyslexia kicked in, I see what you are saying now. Yeah, I might do that, or wait a until Sunday and see what happens… xD

I think my solutions are better tbh, either calling ExecStop with an exit or simply adding it with a && because how I interpret ExecStartPost is that it will start directly AFTER clamdscan has successfully started, not finished.
Could be used for a notification that clamdscan has successfully started to scan, but that I can hear on my fans. xD

No, ExecStop is run when you run systemctl stop ...service, not when the service is stopped by itself.

See: man systemd.unit § Specifiers

Another really handy specifier to use in user-units is %h

For that functionality just add another ExecStart after the command you want to be performed before it, they are invoked/executed in sequence. (after each other on success of the previous one)

ExecStart=clamdscan --fdpass --multiscan --move=%h/.clam/quarantine /redacted/path1/ /redacted/path2/ /redacted/path3/ /redacted/path4/
ExecStart=notify-send -u normal -w -i data-information -a "Clamdscan" 'Done, check for more info:' "journalctl --user --unit %N"
  1. No need for separate log files, because the stdout output is saved in the journal logs…
  2. No need to use absolute paths when the utils are located in the system path, eg. /usr/bin etc…
1 Like

Is this a new change? Previously, you must always give the absolute path.

It is preferred to use absolute paths, but it’s not needed in certain cases, hence it is not new…

  • man systemd.exec
  1. PATHS
    The following settings may be used to change a service’s view of the filesystem. Please note that the paths must be absolute and must not contain a “…”
    path component.

        Takes a colon separated list of absolute paths relative to which the executable used by the Exec*= (e.g.  ExecStart=, ExecStop=, etc.) properties can
        be found.  ExecSearchPath= overrides $PATH if $PATH is not supplied by the user through Environment=, EnvironmentFile= or PassEnvironment=. Assigning
        an empty string removes previous assignments and setting ExecSearchPath= to a value multiple times will append to the previous setting.
  2. Environment Variables Set or Propagated by the Service Manager
    The following environment variables are propagated by the service manager or generated internally for each invoked process:

        Colon-separated list of directories to use when launching executables.  systemd uses a fixed value of
        "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" in the system manager. When compiled for systems with "unmerged /usr/" (/bin is not a symlink to
        /usr/bin), ":/sbin:/bin" is appended. In case of the user manager, a different path may be configured by the distribution. It is recommended to not
        rely on the order of entries, and have only one program with a given name in $PATH.

But then again i could be wrong :smile_cat:
Try and see the magic…

It is not required anymore. It is still recommend to use a full path.
However systemd does not use the same $PATH as your interactive shell. The folders used to check for a binary are set fixed on compilation time.

You can use

systemd-path search-binaries-default

to check the folders that will be used.