Search code examples
bashzshfzf

How can I update content being piped to FZF?


I want to poll something and then pipe results to FZF, such that it gets updated there. Here is an example using CURL:

while true; do curl google.com -s; sleep 2; done | fzf

This stacks-up all the outputs like:

  </BODY></HTML>
  <A HREF="http://www.google.com/">here</A>.
  The document has moved
  <H1>301 Moved</H1>
  <TITLE>301 Moved</TITLE></HEAD><BODY>
  <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
  </BODY></HTML>
  <A HREF="http://www.google.com/">here</A>.
  The document has moved
  <H1>301 Moved</H1>
  <TITLE>301 Moved</TITLE></HEAD><BODY>
  <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
  </BODY></HTML>
  <A HREF="http://www.google.com/">here</A>.
  The document has moved
  <H1>301 Moved</H1>
  <TITLE>301 Moved</TITLE></HEAD><BODY>
  <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">

But I want it to just list one output

  </BODY></HTML>
  <A HREF="http://www.google.com/">here</A>.
  The document has moved
  <H1>301 Moved</H1>
  <TITLE>301 Moved</TITLE></HEAD><BODY>
> <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">

and update it if it changes.


Solution

  • A simple way of dealing with this is with a key-press-reload command:

    curl google.com | fzf --bind 'ctrl-r:reload(curl google.com)'
    

    But that requires pressing CTRL-R to refresh. To make it automated... can wrap the desired command and FZF, and use FZF with fzf -f <query> to only get the results to display. Eg, with node create fzf-watch.js:

    const { exec } = require("child_process");
    const cmd = process.argv[2];
    let query = "";
    let timeout;
    let commandRes = "";
    
    if (!cmd) {
      console.log("A command must be provided as the second argument");
      process.exit(1);
    }
    
    function runCommand(cb) {
      exec(cmd, function (error, stdout, stderr) {
        if (stderr) {
          console.log("standard error", stderr);
        }
        commandRes = stdout;
        cb();
      });
    }
    
    function printOut(/** @type {string} */ str) {
      console.clear();
      console.log(str);
      process.stdout.write(`Query: ${query}`);
    }
    
    function runFzf() {
      if (!query) {
        printOut(commandRes);
        return;
      }
      exec(
        `echo '${commandRes}' | fzf -f "${query}"`,
        function (error, stdout, stderr) {
          if (!error) {
            printOut(stdout);
          } else {
            printOut("");
          }
        }
      );
    }
    
    function runQuery() {
      // Run fzf on existing result in the meantime
      runFzf();
      // Start running the command
      runCommand(runFzf);
      if (timeout) {
        clearTimeout(timeout);
      }
      timeout = setTimeout(runQuery, 5000);
    }
    
    const readline = require("readline");
    
    readline.emitKeypressEvents(process.stdin);
    
    process.stdin.setRawMode(true);
    
    process.stdin.on("keypress", (k, key) => {
      if (key.name === "backspace") {
        query = query.substring(0, query.length - 1);
      }
      else if (key.sequence === "\u0003") {
        process.exit();
      } else {
        query = `${query}${k}`;
      }
      runQuery();
    });
    
    runQuery();
    

    Call it like this to watch the results (called every 5 seconds):

    node ./fzf-watch.js 'curl google.com'