2

I have a little ugly bash script on my Ubuntu machine that contains the lines:

search_command="find -L $(printf "%q" "$search_folder") \( ! -regex '.*/\..*/..*' \) -mindepth 1 2> /dev/null"
for i in "${IGNOREENDINGS[@]}"
do
    search_command="$search_command -not -name \"*$i\""
done
search_command="$search_command | sed 's|^${search_folder}/\?||'"
choice=$(eval "$search_command"|fzf -q "$file_query" -1 --preview "preview $search_folder {}")

The script lets me choose a file, using fzf among the matches of a GNU find command.

It has the following problem: Once I choose a file in the interface of fzf the script closes the fzf interface, so that seems to be done, but then I still have to wait for the find command to complete (verified with top), which somehow takes very long. I'm not really sure why; the files that I want always appear almost instantly.

I included a few extra lines above to avoid an X Y problem. I am happy with anything with the same functionality and quicker execution.

  • Is this on Linux? Or, at least, is this GNU find? Does this work for you: How to stop the find command after first match? – terdon Feb 2 at 17:35
  • @terdon it's Gnu find in bash on Ubuntu 16. Strange, from the link is expect it should quit. Adding "quit" flag is not a viable solution, because I do want to get all output piped to fzf until fzf quits. By the way, I do not get this behavior on another computer of mine, that runs Ubuntu 18 – Bananach Feb 2 at 17:41
  • But didn't you say that the problem is waiting for find? If you need all files, you need to wait for it. While the files you want may appear instantly, it still needs to look at every single file in the directory you have given it to know if there are any more matches. So if you need it to go through all the files, you have to wait for it. It might help if you explained what the script is trying to do since the code isn't very clear. – terdon Feb 2 at 17:50
  • @terdon I need the files as they arrive. As soon as the one arrives that I want, I want it to exit. I edited the question, I hope it's clearer now – Bananach Feb 2 at 17:55
  • So you do want to exit as soon as the first one is found? Please explain what your script is attempting. We won't be able to help much otherwise. – terdon Feb 2 at 17:58
4

There are two possibilities here: either fzf is not actually exiting when you select a file, or find is not exiting when fzf does. If it's the latter one, you can write a script to close find manually when fzf exits.

The way that pipes work in Linux, find does not know that the pipe it is writing to has nothing reading from that pipe until it tries to write to that pipe and fails. As a consequence, if you pick the file after find has already found everything it's going to find, find is no longer writing to the pipe, and so will iterate over the whole file system before exiting.

As an illustration of this, if you make a random file in / and then run find / -name $random_file_name | head -n 1, you will very quickly get all the output you are going to get, but the program will continue to run for a long time.

One way to get around this is by killing the process yourself when it's done. Probably the easiest way to do it in your specific case is a named pipe:

tmp_fifo=`mktemp -u`
mkfifo "$tmp_fifo"
eval "$search_command" > "$tmp_fifo" &
choice="$(fzf -q "$file_query" -1 --preview "preview $search_folder {}" < "$tmp_fifo")" ; kill $!
rm "$tmp_fifo"

This creates a temporary named pipe, find writes to it, and fzf reads from it. But when fzf exits, kill $! is run, where $! stands for the last background process to have been started, in this case find.

  • This sounds exactly like what I was looking for. Also the explanation makes perfect sense. Will try once back at the computer – Bananach Feb 2 at 19:09
  • this will only work if $search_command is a single process; in the case of { ... commands ...; } > fifo &, a kill $! will only kill the outer subshell, not the ... commands .. run by it (in some shells, the last command from a { ...; } will be exec'ed through instead of waited for, but that's not something you can rely on). – mosvy Feb 4 at 9:11
  • @mosvy $search_command is find, so it is a single process. If it isn't, you can do kill -- -$! to kill the subshell and its children. – Chris Feb 4 at 10:51
  • 1. Yes, it works in this special case, I'm not disputing that 2. no, when used in a subshell or script there will be no job control and no separate process group for ... & so kill -- -$! won't kill anything at all. – mosvy Feb 4 at 10:56
  • @mosvy Try it yourself. {sleep 1000 ; foo ; } & sleep 1 ; kill -- -$! – Chris Feb 4 at 11:17
0

That other answer looks fine, the downside is you need to manage the fifo. With Bash's coproc you can solve the problem without a named fifo.

(Note: to make this answer address the general problem rather than your specific one, I dropped your variables, eval and similar stuff).

coproc find ...         # this silently runs in background, no `&' nor explicit redirection needed
<&${COPROC[0]} fzf ...  # piping from `find' to `fzf'
kill $!                 # killing the last background process, i.e. `find'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.