我正在编写一个脚本,该脚本在系统中搜索文件,然后对每个文件进行一些健全性检查,如果它们通过了这些检查,我想在 fzf 中显示它们。当在 fzf 中单击该项目时,我想用程序运行该文件。
到目前为止我有:
dir="/path/to/dir"
fd . $dir --size +1MB | while read -r line; do
file_type=$(file -b "$line")
echo "$line" | fzf
if [[ "$file_type" == "data" ]]; then
echo "$file_type"
fi
done
基本上,我在指定目录中搜索大于 1MB 的文件。对于每个文件,我运行文件命令并检查命令输出是否返回“数据”。如果是的话,我想将它添加到 fzf 列表中。然后,当我单击该项目时,我想使用应用程序运行具有完整路径的文件:例如myapp /path/to/file/selected/in/fzf
。
到目前为止的问题是 fzf 阻塞,我无法填充循环中的列表。我真的必须先将所有内容添加到数组中,然后将其通过管道传输到 fzf 中吗?理想情况下,这应该并行发生,也就是说,我想在搜索仍在进行时动态添加新项目,而不是等待它完成。
我也不知道以后如何运行所选文件。有人可以帮我弄这个吗?
答案1
fzf
不会阻碍你的思考方式。它完全能够动态添加到列表中。您可以在以下示例中看到这一点(pv
如果未安装,请先安装):
find / | pv -qlL 1 | fzf
其中pv
每秒输出一行,这些行出现在 中fzf
,您可以上下移动选择,没有任何阻挡。
您的代码的问题是您在循环fzf
内运行while read …
。对于每一行,您单独运行一个fzf
仅获取这一行的代码。循环只有在fzf
完成后才能继续。所以这并不是拒绝阅读更多内容,而是fzf
拒绝阅读更多内容。这是关于fzf
处于循环内部。
基本上你想要这样的东西:
find … | data_filter | fzf
其中data_filter
是一段过滤行的代码。它可以是一个 shell 循环:
find … | while IFS= read -r line; do …; done | fzf
作为过滤器,循环应该将您不想过滤掉的所有行(并且仅这些行)打印到其标准输出。它可以是一个名为 的 shell 函数data_filter
。在你的情况下,这是正确的功能:
data_filter() {
while IFS= read -r line; do
[ "$(file -b "$line" 2>/dev/null)" = data ] && printf '%s\n' "$line"
done
}
然后将其用作管道中的过滤器。
find … | data_filter | fzf
现在我们有了应该可以正常工作并打印您选择的任何路径名的管道。要对文件执行某些操作,请使用以下命令之一:
find … | data_filter | fzf | xargs …
,其中xargs
配置为按原样读取整行。对于 GNUxargs
,如果您要运行的工具是ls -l
,则如下所示:find … | data_filter | fzf | xargs --no-run-if-empty -d '\n' -I{} ls -l {}
但是因为
xargs
默认情况下会解释引号并执行其他操作(例如拆分),所以您需要很好地了解它的选项才能在这种情况下使用它。我承认我从未掌握过xargs
,并且我不确定我在这里使用了最好的选项集。我的观点是:像这样的简单调用… | fzf | xargs ls -l
在很多情况下都会中断。f="$(find … | data_filter | fzf)"
"$file"
,然后在任何你想要的地方使用。一个优点是你可以知道 的退出状态fzf
。理论上的缺点是$()
删除尾随换行符。实际上,在我们的例子中,带有尾随(或任何)换行符的路径名无论如何都不能很好地通过data_filter | fzf
。例子:
f="$(find . | data_filter | fzf)" status="$?" [ "$status" = 0 ] && ls -l "$f"
由于管道中的所有工具都使用换行符来分隔条目,因此带有换行符的路径名将破坏代码。
要处理带有换行符的路径名,您需要使整个管道使用空终止(而不是换行符终止)条目。
首先,您需要
find … -print0
(或等效fd
命令)。请注意,某些实现find
不支持-print0
(-exec printf '%s\0' {} +
可以替代)。那么新的过滤函数应该是:
data_filter0() { while IFS= read -r -d '' line; do [ "$(file -b "$line" 2>/dev/null)" = data ] && printf '%s\0' "$line" done }
下次使用
fzf --read0 --print0
。最后
xargs -r0 …
。 (替代方案 与$()
很麻烦,我不会详细说明;在这种情况下更喜欢xargs -r0
。)请注意,这些选项不可移植,您xargs
可能支持也可能不支持它们。作为奖励,xargs -r0
可以很好地进行多项选择fzf -m
。
管道示例:
find . -print0 | data_filter0 | fzf -m --read0 --print0 | xargs -r0 ls -l
注释和有用的链接:
一般来说,过滤器可以内置
find …
(感谢find -exec
)。我没有这样做是因为你似乎想使用fd
并且我不太了解这个工具。如果您提早选择一个项目(即在
find
过滤器完成之前),那么fzf
管道中的每个稍后都可能会在find
退出之前退出。过滤器fzf
只有在尝试写入后才会注意到不再存在;同样,find
在尝试写入后会注意到(比较这个答案)。这意味着find
工作时间可能比需要的时间长,从而阻止 shell 继续执行脚本中的下一个命令(或者阻止显示提示,如果是交互式的)。您可以在后台运行某些部分:( find . -print0 | data_filter0 & ) | fzf -m --read0 --print0 | xargs -r0 ls -l
这不会阻止
find
过滤器在fzf
退出后工作,但整条生产线不会停止。外壳在ls
完成其工作后将立即继续前进。后台进程迟早会终止。正确引用。你的问题中没有引用
$dir
。