Estou escrevendo um script que procura arquivos no sistema e, em seguida, para cada arquivo, faz algumas verificações de sanidade e, se eles passarem, quero exibi-los em fzf. Quando o item é clicado em fzf, quero executar o arquivo com um programa.
Até agora tenho:
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
Basicamente, procuro arquivos maiores que 1 MB no diretório especificado. Para cada arquivo eu executo o comando file e verifico se a saída do comando retorna "data". Se isso acontecer, quero adicioná-lo à lista fzf. Então, quando clico no item, quero executar o arquivo com o caminho completo com um aplicativo: por exemplo myapp /path/to/file/selected/in/fzf
.
O problema até agora é que o fzf está bloqueando e não consigo preencher a lista no loop. Eu realmente tenho que adicionar tudo a uma matriz primeiro e depois canalizar isso para fzf? Idealmente, isso deve acontecer em paralelo, ou seja, quero adicionar novos itens rapidamente enquanto a pesquisa ainda está em andamento, em vez de esperar que ela seja concluída.
Também não sei como executar posteriormente o arquivo selecionado. alguém poderia me ajudar com isso?
fzf
não bloqueia a maneira como você pensa. É totalmente capaz de adicionar à lista em tempo real. Você pode ver isso no exemplo a seguir (instalepv
primeiro se não estiver instalado):onde
pv
sai uma linha por segundo, as linhas aparecem emfzf
, você pode mover a seleção para cima e para baixo e nada bloqueia.O problema com seu código é que você executa
fzf
dentro dowhile read …
loop. Para cada linha, você executa um separadofzf
que obtém apenas essa única linha. O loop pode continuar somente após o términofzf
. Então não é quefzf
se recusa a ler mais; é sobrefzf
estar dentro do loop.Basicamente você quer algo assim:
onde
data_filter
é um pedaço de código que filtra linhas. Pode ser um loop de shell:Como filtro, o loop deve imprimir em seu stdout todas as linhas que você não deseja filtrar (e apenas essas linhas). Pode ser uma função shell chamada
data_filter
. No seu caso, esta é a função correta:E então você o usa como um filtro em um tubo.
Agora temos o pipeline
find … | data_filter | fzf
que deve funcionar bem e imprimir qualquer nome de caminho que você escolher. Para fazer algo com o arquivo, use um destes:find … | data_filter | fzf | xargs …
, ondexargs
está configurado para ler as linhas completas como estão. Com GNUxargs
e se a ferramenta que você deseja executar forls -l
, ficaria assim:Mas como
xargs
por padrão interpreta aspas e faz outras coisas (como dividir), você precisa conhecer bem suas opções para usá-lo em um caso como este. Admito que nunca domineixargs
e não tenho certeza se usei o melhor conjunto de opções aqui. Meu ponto é: uma simples invocação como… | fzf | xargs ls -l
irá quebrar em muitos casos.f="$(find … | data_filter | fzf)"
, depois use"$file"
onde quiser. Uma vantagem é que você pode saber o status de saída dofzf
. Uma desvantagem teórica é$()
tirar novas linhas à direita. Na prática, no nosso caso, um nome de caminho com uma nova linha à direita (ou qualquer outra) não passa bem dedata_filter | fzf
qualquer maneira.Exemplo:
Como todas as ferramentas no pipeline usam novas linhas para separar entradas, os nomes de caminho com novas linhas quebrarão o código.
Para lidar com nomes de caminho com novas linhas, você precisa fazer todo o pipeline funcionar com entradas terminadas em nulo (em oposição às terminadas em nova linha).
Primeiro de tudo, você precisa (ou comando
find … -print0
equivalente ).fd
Observe algumas implementações defind
não suportam-print0
(-exec printf '%s\0' {} +
pode ser uma substituição).Então a nova função de filtragem deve ser:
Próxima utilização
fzf --read0 --print0
.Finalmente
xargs -r0 …
. (A alternativa com$()
é problemática e não vou elaborar; neste caso, prefiraxargs -r0
.) Observe que essas opções não são portáteis e vocêxargs
pode não suportá-las. Como um bônusxargs -r0
funcionará bem com seleção múltipla defzf -m
.Um exemplo de pipeline:
Notas e links úteis:
Em geral, o filtro pode ser incorporado
find …
(graças afind -exec
). Eu não fiz isso porque parece que você quer usarfd
e eu não conheço essa ferramenta bem o suficiente.Se você escolher um item mais cedo (ou seja, antes
find
e o filtro terminar), entãofzf
e todos os itens posteriores no tubo podem sair antes dasfind
saídas. O filtro perceberá quefzf
não existe mais somente depois que tentar escrever; da mesma formafind
notará depois que tentar escrever (compare esta resposta ). Isso significafind
que pode funcionar por mais tempo do que o necessário, impedindo que o shell passe para o próximo comando no script (ou exiba o prompt, se interativo). Você pode executar algumas partes em segundo plano:Isso não impedirá
find
nem o filtro de funcionar após asfzf
saídas, mas a linha inteira não irá parar. O shell seguirá em frente imediatamente apósls
fazer seu trabalho. Os processos em segundo plano terminarão mais cedo ou mais tarde.Cite certo . Na sua pergunta está sem aspas
$dir
.Por que é
printf
melhor do queecho
? .Compreensão
IFS= read -r line
.Como uso bytes nulos no Bash?