Terminate a text search in less(1) and continue to read thru a Unix pipe

lesspipe

gunzip < 100terabytes.txt.gz | less

less(1) reads as many lines as necessary to fill your screen and then stops to read(2). As a result, gunzip(1) is blocked on write(2) as the pipe becomes full.

As you scroll down, less(1) issues read(2) again and again, and as the pipe is consumed, gunzip(1) is allowed to issue write(2). You have a full flexibility in going back and forth here (assuming gunzip < 100terabytes.txt.gz doesn't complete.)

Everything is fine so far.

You can start a text search in less(1) with /. But when the search string is not found in 100terabytes.txt, less(1) essentially becomes unresponsive. You can terminate the search with Ctrl-C. But it seems to shutdown the pipe between gunzip(1) and less(1). I don't like this. I want to manually scroll down to consume more lines from gunzip(1) after I give up a text search. It this possible?

I'm not asking for an advice like gunzip < 100terabytes.txt.gz | grep pattern | less

Update

You can experiment with od -v /dev/zero | less

Best Answer

When you press Ctrl+C, that's the whole shell job (process group) that receives the SIGINT, less intercepts it to abort the search, but gunzip will terminate. To avoid that, you could do:

(trap '' INT; gunzip < file.gz) | less

so that gunzip ignores the SIGINT, but note that you won't be able to interrupt gunzip anymore after that.

For gunzip, that's probably fine, as all you need to do is quit less after which gunzip will die of a SIGPIPE the next time it writes something, but for applications that just hang without outputting something, that would be more of a problem (you'd still be able to use Ctrl+Z for SIGTSTP or Ctrl+\ for SIGQUIT though).

Also note that some commands like pv or ping install their own SIGINT handler which would revert our trap '' INT.

You can create a function to save typing like:

iless() {
  (trap '' INT; "$@") | less
}

iless gunzip < file.gz

Or:

noint() (trap '' INT; "$@")

noint gunzip < file.gz | less

But note that for:

gunzip < file.gz | grep foo | less

you'd need to write it:

noint gunzip < file.gz | notint grep foo | less

or:

noint eval 'gunzip < file.gz | grep foo' | less

or:

iless eval 'gunzip < file.gz | grep foo'

An alternative is to use process substitution:

less -f <(gunzip < file.gz | grep foo)

or (though not in zsh):

less < <(gunzip < file.gz | grep foo)

In those cases, the shell doesn't include the commands inside the process substitution in a the foreground process group (except in the second case for zsh). Their process group stays the same as the shell's. That means that they don't receive a SIGINT when you press Ctrl+C.

Note that those processes won't be affected by Ctrl+Z or Ctrl+\ either.

Tested in zsh, ksh93 and bash.

Related Question