动态更新fzf项目

动态更新fzf项目

我正在编写一个脚本,该脚本在系统中搜索文件,然后对每个文件进行一些健全性检查,如果它们通过了这些检查,我想在 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配置为按原样读取整行。对于 GNU xargs,如果您要运行的工具是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

  • 为什么printf优于echo

  • 理解IFS= read -r line

  • 如何在 Bash 中使用空字节?

相关内容