0

I have the following bash script that I'd like to use as a fuzzy file opener. I create a fifo, spawn a new terminal with fzf running and redirect fzf's output to the fifo. I then call a function that reads from the fifo and opens the files.

My problem is that the while loop inside the open function never ends. How can I close the fifo once all the lines have been read?

#!/usr/bin/env bash

FIFO=/tmp/fuzzy-opener [ -e "$FIFO" ] || mkfifo "$FIFO" exec 3<> "$FIFO"

function open { while read file; do # open every $file based on its mime-type done <&3 echo 'done' # this is never reached }

alacritty -e sh -c "fzf -m >&3"
&& open

noibe
  • 387
  • 5
  • 17

2 Answers2

3

The pipe will show an EOF (return from read() with zero bytes read), if the writing side is closed. But I think what happens is that since <> opens the pipe in read-write mode, the pipe always has a writer, the shell holding the file handle open.

I think you should be able to skip opening the pipe in the shell, and just do this instead:

alacritty -e sh -c "fzf -m >$FIFO" && open < "$FIFO"

Or more properly:

alacritty -e sh -c 'fzf -m >"$1"' sh "$FIFO" && open < "$FIFO"

I'm assuming here that the construct in the question works otherwise, which it should if alacritty spawns a terminal in the background and exits immediately. If it doesn't, you'd need something like alacritty -e sh ... & open < "$FIFO" to run both parts at the same time.

ilkkachu
  • 138,973
  • @KamilMaciorowski, or alacritty ... & open < to run alacritty in the background. But since it seemed to work for them in the question, I suppose alacritty just spawns a window to run the shell through a background process and returns immediately. – ilkkachu Apr 01 '21 at 12:42
  • @KamilMaciorowski, well, the question already had the && there, and the only problem they mention there is that the loop never ends. So yes, I expect it works for them like that. Also yes, it won't work if the first command doesn't exit immediately, in that case, you'd need something else. – ilkkachu Apr 01 '21 at 12:59
  • The construct in the question works regardless if alacritty returns immediately, because there is <> and fzf doesn't block (unless maybe it fills the fifo buffer). You fixed the original problem by removing <> but this may cause fzf to block, depending on whether alacritty returns early or not. So it's not a case the OP does not mention a problem that might be there. It's a case where your (reasonable) fix may induce a problem that wasn't there. +1 nonetheless. The current revision is useful even if alacritty waits for sh. – Kamil Maciorowski Apr 01 '21 at 13:06
  • @KamilMaciorowski, ah, hmm... You have a point. Seems to me it depends on how those two work, exactly. And at least I now need to test it to reason about it any further... – ilkkachu Apr 01 '21 at 13:21
  • 1
    I was nitpicking because your answer appeared when I was testing and composing mine. That's why I was familiar with some potential problems. Still I decided not to publish a separate answer, because I strongly believe improving a decent answer is better than posting virtually the same answer with minor improvement added. Keep up the good work. – Kamil Maciorowski Apr 01 '21 at 13:27
  • @ilkkachu If I remove the exec 3<> "$FIFO" line, fzf blocks waiting for a reader (same behaviour as echo foo > $FIFO). I fixed that by backgrounding alacritty like you suggested, but when does that process exit? – noibe Apr 01 '21 at 14:15
2

I would suggest a couple of alternatives, because:

  • based on the script in your question, a FIFO seems actually not needed;
  • in principle, since you are using Bash, you could take advantage of the NUL character as a delimiter (the only byte that is not allowed in a POSIX file path); unfortunately, though, fzf does not seem to work with file names containing newline characters;
  • reading from a file in /tmp may pose a major security issue: if someone else created /tmp/fuzzy-opener (as a regular file), your script would happily apply open to its content (though, on some systems, opening a not-owned file in a word-writable sticky directory using exec 3<> will raise an error).

You may use:

function open {
  while IFS= read -r -d '' file
  do
    echo "$file"    # Replace with the actual open action
  done
}

alacritty -e sh -c 'fzf -m --disabled --print0 >&3' 3>&1 | open

which can be made portable by removing -d '' and --print0 (losing nothing, given the aforementioned limitation of fzf); or, using an array to store the selected file names:

function open {
  for file
  do
    echo "$file"    # Replace with the actual open action
  done
}

mapfile -t -d '' toopen < <(alacritty -e sh -c 'fzf -m --print0 >&3' 3>&1) && open "${toopen[@]}"

In both cases, the main point is that fzf's output is redirected to a new file descriptor obtained duplicating the writing end of a pipe and thus connected to a reader command.

fra-san
  • 10,205
  • 2
  • 22
  • 43