(trap : INT; echo "$(less <<< 'some text' >/dev/tty; echo done)")
less
will devolve into a kind of (un)fancy cat
when its stdout is not a tty, as when used in a command substitution (where its stdout is a pipe) [1].
When testing in an interactive bash (where the job control is enabled by default), the (...)
subshell will make sure that that the less
command is run as part of a proper foreground job in the terminal, without which it may not be possible to suspend it with ^Z
, terminate it with ^C
and it will misbehave in other ways [2].
The trap : INT
will cause the subshell to ignore a ^C
, without passing that disposition to its childen.
If the command is part of a shell script, all the processes will be run as part of the same process group / job, but the (...)
may still be useful to limit the scope of the INT
trap.
[1] You cannot even use it to print binary data nicely escaped when its stdout is not a tty:
$ printf '\xee' | less -FXR
<EE>
$ printf '\xee' | less -FXR | cat
�$
[2] There are many possible scenarios for what may happen, all bad. For instance, in
$ ls -d $(less <<<'some text' >/dev/tty; pwd)
bash
will fork a separate process in which to exec the ls
binary, and will run the $(...)
process substitution as a child of it before moving it into a separate process group / job and executing the binary.
When you press ^Z
inside less
, it will be handled by less
normally, but will either fail to stop it (if the shell and consequently less
's job is the session leader), or will cause only less
, but not its parent process to stop.
When you press ^C
inside less
, it will caught and handled by less
, but a SIGINT
will also be sent to the main shell (which is foreground job on the terminal), which will cause it to return the next prompt, and the bash's readline and less
to compete for input and change the terminal properties unaware of each other, resulting in a complete cockup.
Note
All this applies to any program run from a command substitution, not just to the $(less ...)
example, eg.
$ cat >foo <<'EOT'; chmod 755 foo
t=$(mktemp)
vi "$t" </dev/tty >/dev/tty 2>&1
echo "$t"
EOT
$ cat $(./foo)
# ^Z doesn't work in vi