Bash

動態更新 fzf 項目

  • September 25, 2021

我正在編寫一個腳本,在系統中搜尋文件,然後對每個文件進行一些完整性檢查,如果它們通過了,我想在 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 嗎?理想情況下,這應該並行發生,也就是說,我想在搜尋仍在進行時動態添加新項目,而不是等待它完成。

我也不知道以後如何執行選定的文件。有人可以幫我弄這個嗎?

fzf不會阻礙您的思維方式。它完全能夠即時添加到列表中。您可以在以下範例中看到這一點(pv如果未安裝,請先安裝):

find / | pv -qlL 1 | fzf

其中pv每秒輸出一行,這些行出現在 中fzf,您可以上下移動選擇並且沒有任何阻塞。

您的程式碼的問題是您fzfwhile read …循環內執行。對於每一行,您執行一個單獨fzf的僅獲取該單行的行。循環只有在fzf完成後才能繼續。所以不是fzf拒絕閱讀更多;這是關於fzf在循環中。

基本上你想要這樣的東西:

find … | data_filter | fzf

wheredata_filter是一段過濾行的程式碼。它可以是一個 shell 循環:

find … | while IFS= read -r line; do …; done | fzf

作為過濾器,循環應該將您不想過濾掉的所有行(並且只有這些行)列印到其標準輸出。它可以是一個名為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我的觀點是:在許多情況下,一個簡單的呼叫 like會中斷。

  • 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完成其工作後立即繼續執行。後台程序遲早會終止。

引用自:https://unix.stackexchange.com/questions/670006