As far as I know %I is designed to be a single string rather than multiple. For now it seems to me the answer to "is there any way to achieve %I unpacking, without using another wrapper like shell?" is "no".
A shell is not a bad tool to perform splitting, but what you straightforwardly did is not necessarily the best. The rest of this answer possibly improves what you did; it does use a shell.
A better and safer way to use a shell for what you want to do is like:
ExecStart=/usr/bin/sh -fc 'exec /path/to/my/exec $@' sh %IThe way is better because:
- (The first)
execwill make the shell replace itself with (your)exec, so ultimately (your)execwill be the child, not a grandchild of systemd. In general it's good to make the main process of the service the direct child of systemd. I guess some implementations ofshmay implicitly doexecfor the very last command as an optimization. With the explicitexecwe make the shell do this for sure.
The way is better and safer because:
%Ihere is like{}in the context of this answer: Never embed{}in the shell code! It shall not be embedded in shell code.-fis a shortcut forset -o noglob, it disables pathname expansion (globbing).
Are these really needed? The documentation of systemd states that
The "unit name prefix" must consist of one or more valid characters (ASCII letters, digits, ":", "-", "_", ".", and "\")
The instance name will be used to create the name of the full unit and it seems the above rule applies to the whole resulting string. Still with proper escaping (see systemd-escape) you can pass other characters as well (e.g. ; or *), then your %I (as opposed to %i) will expand to unescaped instance name. I'm sure it's not new to you; I'm sure you're already passing spaces as \x20.
I have tested and confirmed that in case of %I embedded in shell code (like in the question) one can inject shell code and/or trigger globbing.
You probably do not deliberately want to inject code or to trigger globbing by crafting an instance name that does this. I totally don't know what exactly your exec expects as arguments, maybe you want some of them to contain ;, &, *, [ or so. This answer allows you to do this safely.
But if you want your exec to be able to get argument(s) that contain space(s), tab(s) or newline(s) then our safer way is actually a bad way. Embedding %I in shell code (like you did) will allow you to pass (quoted or escaped) spaces and such. Then you must remember the instance name will be interpreted as shell code.
If you want your exec to be able to get argument(s) that contain spaces(s) and/or such, and if you don't want the instance name to be interpreted as shell code, and if there is a character that is unlikely to ever appear inside any argument to your exec, then consider the following example (where : has been chosen to be the character):
ExecStart=/usr/bin/sh -fc 'IFS=:; exec /path/to/my/exec $@' sh %IIn instance names you then use : to separate arguments one from another. If your exec (or any descendant) relies on IFS being the default then you may want to remove IFS from its environment (so the default value will be used):
ExecStart=/usr/bin/sh -fc 'IFS=:; exec env -u IFS /path/to/my/exec $@' sh %I(env will replace itself with your exec, so ultimately your exec will become the child of systemd.)