Bash – Why would a backgrounded job with `| less` be stopped, while the other one without it is running

background-processbashpipe

$ pdfgrep -R -i spark . | less &
$ pdfgrep -R -i spark . &

$ jobs
[3]-  Stopped                 pdfgrep -R -i spark . | less
[4]   Running                 pdfgrep -R -i spark . &
  1. Why would the one with | less be stopped, while the other one
    without it is running?

    The stopped backgrounded job doesn't read from stdin. So that can't be the reason.

  2. The reason that I background the jobs is that I can do something
    else in the same terminal session.

    The reason that I pipe to less is because I don't want the output
    to stdout messes up the screen of my terminal session when I am
    doing something else.

    Is there some way to achieve the two goals above? I slightly prefer
    not saving output to a file over saving output to a file, because it takes a little more to remember the file, read and delete them.

Thanks.

Best Answer

Let's look more closely at what's happening to less:

$ pdfgrep -R -i spark . | strace less &
[...]
open("/dev/tty", O_RDONLY|O_LARGEFILE)  = 3
ioctl(3, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
ioctl(3, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig -icanon -echo ...}) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGTTOU {si_signo=SIGTTOU, si_code=SI_KERNEL} ---
--- stopped by SIGTTOU ---

Job control restricts the processes in a background job from performing certain operations on the controlling terminal.

  • If a background process tries to read from the terminal, it will be sent a SIGTTIN signal, which typically stops (pauses) the process.

  • If a background process tries to set a terminal's parameters, it will be sent a SIGTTOU signal, which also typically stops the process. That's what is happening here with the TCSETSW ioctl. The less program tries to put the terminal into raw mode soon after it starts, even before it knows whether it has anything to display.

    There is a good reason for this: you don't want a background job asynchronously changing your terminal so that, for example, raw mode is on and echo is off. (A background process can get terminal parameters with the TCGETS ioctl without being stopped - see the listing above.)

  • If a background process tries to write to the terminal and the terminal has the tostop flag set, it will be sent the SIGTTOU signal.

You probably don't have the tostop flag set (run stty -a to check). If you don't, a background command like pdfgrep -R -i spark . & that doesn't change any terminal settings will be able to write to your terminal whenever it tries.

You also wrote:

The reason that I pipe to less is because I don't want the output to stdout messes up the screen of my terminal session when I am doing something else

The less program is ultimately going to send output to the terminal, one screenful at a time. If you run stty tostop before pdfgrep | less &, or before pdfgrep &, then they will only output to your terminal when they are in the foreground.

Related Question