Consider a script like this:
$ cat example.sh
#! /usr/bin/env bash
for i in {1..90}
do
printf '%s\n' "$i"
done
sleep 10
printf '91\n'
sleep 10
printf 'done\n'
and suppose the output is piped to less, like so:
$ bash example.sh | less
If I scroll down as far as line 90,
I can scroll back up again, search,
and use any other interactive commands that less
provides.
However, as soon as I try to go past line 90 with e.g. j
or Ctrl-N
,
less
stops responding to interactive commands
until another line of input is available.
And if I try to scroll a full pageful past line 90 with e.g. spacebar,
less
stops responding to interactive commands
until a full page of input is available
or it receives EOF.
This can be undesirable if I want to look at previous output
and don't realize I have just gone over the available lines
and must wait for more lines to appear,
which could require an arbitrary amount of time.
If I use Ctrl-C to send SIGINT,
I can immediately get interactivity again,
but then less
will stop listening for more input from the pipe.
The script is just an easily reproducible example,
but it could be replaced by any long-running command
that slowly generates lines of output, such as finding broken symbolic links:
$ find $HOME -xtype l | less
or world-readable permissions in my home directory:
$ find $HOME -perm 777 | less
or any number of other slow, resource-intensive commands that send lines to stdout
.
Is there any way I can tell less
to stop waiting for more input
and regain interactive commands
without waiting for the required lines of input
to be generated from the pipe?
Best Answer
In
The whole pipeline is put in foreground (the foreground process group has both the process running
bash
and all the processes it spawns itself, andless
), so when you press Ctrl + C, all ofbash
,sleep
andless
receive the SIGINT.sleep
will die upon receiving it, so willbash
¹.less
intercepts that SIGINT where it's handled as cancel current action.But if
bash
hadn't died, you would have been able to resume reading insideless
.So what you can do is prevent
bash
from dying of SIGINT:Then you'll be able to use Ctrl + C to interrupt
less
without affectingbash
and the processes launched by that script.To stop the script, you can use Ctrl + \ which sends a SIGQUIT (beware it may cause a core dump if you haven't set the core dump size limit to 0), or Ctrl + Z to suspend the job (SIGTSTP) followed by
kill %
to kill it (with SIGTERM).¹ here, as
bash
realisedsleep
died of a SIGINT; it would have been different ifsleep
had handled that SIGINT and exited normally, that's a special SIGINT handling done bybash
and a a few other shells.