有没有办法在将 `find` 的结果传递给 `-exec` 之前 "map"?

Is there a way to "map" the results of `find` before passing them to `-exec`?

在下面的命令中,我得到了一个文件路径列表,我想在对每个文件路径执行 运行 多个 -exec 命令之前获取每个文件路径的基本名称。

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" \;

有没有办法不必每次都调用 basename {} 来执行此操作?

回答

find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 -I {} bash -c "echo 'stopping' {}; systemctl stop {}; systemctl disable {}"

说明

首先看一下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

如果您使用 -exec,那本质上就是一张地图。例如

find /usr/local/lib/systemd -type f -exec basename {} \;
file1.service
file2.service
file3.service

然后您可以将其通过管道传输到 xargs 并使用 -L 选项一次将 N 个参数传递给命令。在这种情况下,-L1 将对输入中的每个 1 行重复 xargs 命令。查看命令仅打印“test”和文件名的示例:

find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 echo "test"
test file1.service
test file2.service
test file3.service

您可以使用 在单个 bash 命令中多次替换参数,如下所示:

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

最后,您可以使用bash -c "..."到运行多个命令作为一个命令。将其用作 xargs 命令,如下所示:

find /usr/local/lib/systemd -type f -exec basename {} \; | xargs -L1 -I {} bash -c "echo 'stopping' {}; systemctl stop {}; systemctl disable {}"

systemctl 采用 glob 模式,所以你可以这样做:

echo stopping everything
systemctl stop \*
systemctl disable \*

引用模式很重要,这样它就不会被 shell 扩展。

另请注意 man systemctl > 参数语法:“shell-style glob 将与当前内存中所有单位的主要名称相匹配”。因此在某些情况下它可能不等同于您的查找方法。

为了更笼统地回答您的问题,更好地控制 find 生成的文件列表的一个好方法是将其传递给 shell 循环:

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' _ {} +

此外,在这种情况下,因为 systemctl 可以采用多个单位参数,所以您可以这样做:

-exec bash -c '
systemctl stop "${@##*/}"
systemctl disable "${@##*/}"' _ {} +

这会调用 systemctl 两次*,而不是多次调用,这样效率更高。 ${@##*/} 去除每个位置参数的前缀直到最后一个斜杠,与 basename.

相同

* 如果文件列表很大,find 可能会多次调用 -exec 命令 (bash)。