Tiny: Grepping a program's output while still displaying the progress
I needed to perform an rsync operation, which displayed the progress while running, filtering out some lines matching a pattern.
This isn’t solvable with a oneliner, but I wanted to solve it nonetheless, in a simple way; this tiny article will show how.
Content:
The requirement
I often use rsync to keep some data in sync. In some cases I need to manually inspect the output, filtering out some noise (lines matching a certain pattern); also, I need to view the progress update.
The problem
It’s not possible to use grep - the logic is not simply “inspect a line, and if it doesn’t match a pattern, print it”; progress is displayed by using special characters (the carriage return (\r)) that need to be printed immediately.
Following this logic, grep should hypothetically print the characters immediately, then, when a newline is found, inspect the line, and erase or print according to the match; this functionality is not supported.
The solution
While a trivial (oneliner) solution is not possible, by using a scripting language, we can still implement a simple solution.
This is the implementation using Ruby:
  stdbuf -o0 rsync \
    --itemize-changes <other_params...> \
    | ruby -e '
      STDIN.each_char.each_with_object("") do |char, current_line|
        print char
        if char == "\n"
          print "\e[A\e[K" if current_line =~ /^[.<>][fd]\.\.[.t][.p]\.\.\.\.\./
          current_line.clear
        else
          current_line << char
        end
      end
    '
Explanation of the most important concepts:
stdbuf -o0sets rsync output to unbuffered, forcing it to send individually each character to the pipe; normally, the output is buffered for performance reasons"\e[A\e[K"is an ANSI escape sequence, that goes up one line, and clears the landing line^[.<>][fd]\.\.[.t][.p]\.\.\.\.\.is a pattern that matches lines indicating entries that have permissions or modification time changed (in the format enabled by--itemize-changes; for the details, see the man page, viaman rsync | less +/--itemize-changes, +n); note that the regex could be simplified, but with the current structure, it’s more readable
Conclusion
While the solution is not a oneliner, it’s still compact and intuitive; additionally, it can be easily separated into a script (e.g. grep_progress) that can be reused in such cases.
Happy scripting!