In the following command, I get a list of file paths and I want to get the basename for each before running multiple -exec
commands on each of them.
find /usr/local/lib/systemd -type f \
-exec bash -c "basename {} | xargs echo 'stopping'" \; \
-exec bash -c "basename {} | xargs systemctl stop" \; \
-exec bash -c "basename {} | xargs systemctl disable" \;
Is there a way to do this without having to call basename {}
each time?
CodePudding user response:
Answer
find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 -I {} bash -c "echo 'stopping' {}; systemctl stop {}; systemctl disable {}"
Explanation
First, take a look at the output of find
find /usr/local/lib/systemd -type f
/usr/local/lib/systemd/file1.service
/usr/local/lib/systemd/file2.service
/usr/local/lib/systemd/file3.service
If you use -exec
, that's essentially a map. For example
find /usr/local/lib/systemd -type f -exec basename {} \;
file1.service
file2.service
file3.service
You can then pipe that to xargs
and use the -L
option to pass in N arguments at a time to a command. In this case, -L1
will repeat the xargs
command for every 1
line in the input. Look at an example where the command just prints "test" and the filename:
find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 echo "test"
test file1.service
test file2.service
test file3.service
You can use the -I
option of xargs
to substitute in the arguments multiple times in a single bash command, like this:
find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 -I {} echo hello {} world {}
hello file1.service world file1.service
hello file2.service world file2.service
hello file3.service world file3.service
Finally, you can use bash -c "..."
to run multiple commands as one command. Use this as the xargs
command, like this:
find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 -I {} bash -c "echo 'stopping' {}; systemctl stop {}; systemctl disable {}"
CodePudding user response:
systemctl
takes glob patterns, so you can do:
echo stopping everything
systemctl stop \*
systemctl disable \*
It's important to quote the pattern so it's not expanded by the shell.
Also note this from man systemctl
> Parameter Syntax: "shell-style globs will be matched against the primary names of all units currently in memory". So it may not be equivalent to your find approach in certain cases.
To answer your question more generically, a good way to get more control over the file list generated by find
is to pass it to a shell loop:
find /usr/local/lib/systemd -type f -exec bash -c '
for i do
i=$(basename "$i")
echo "$i"...
systemctl stop "$i"
systemctl disable "$i"
done' _ {}
Also, in this case, because systemctl
can take multiple unit arguments, you could do this instead:
-exec bash -c '
systemctl stop "${@##*/}"
systemctl disable "${@##*/}"' _ {}
This invokes systemctl
twice*, instead of many times, which is more efficient. ${@##*/}
strips the prefix of each positional parameter up to the last slash, identically to basename
.
* find may invoke the -exec
command (bash
) multiple times if the file list is large.