1

If I do ls | less, ls detects that it isn't connected to a terminal, which is fair enough. Except that less is connected to a terminal. In this setup, ls could produce the coloured and columnised output targetting terminals, and less could deal with it properly. You can get colours working properly with ls --color=force | less -r, but that's more typing, and it doesn't do columns.

What would be cool is if there were a switch on less that tells whatever is hooked up to it to treat it like a real terminal. So you could do ls | less -T and get the coloured and columnised listing in less.

  1. Has anything like this been done?

  2. Could less or a pager like it actually do that by itself, or would this require cooperation from the shell? For instance, would the shell need to set up a pseudoterminal for ls to connect to?

2 Answers2

7

It's the shell that sets the pipeline up. less is not involved in that.

When you do ls | less, the shell starts ls and less concurrently after having made the stdout of the process that will eventually execute ls the writing end of a pipe and the stdin of the one that will execute less the reading end of that same pipe.

ls detects that its stdout is not a tty device, and alters its behaviour accordingly. There's nothing that less can do about it.

What the shell could do is use a pseudo-terminal pair instead of a pipe to connect the two processes, but that would be a bad idea, as pseudo-terminals were not designed for that. For one, the tearing down of the connection would be problematic. less would probably be confused if its stdin was a pseudo-terminal master.

Another approach would be to get a 3rd process that reads the output of ls via a pseudo-terminal pair and feeds it to less via a pipe like with @JdeBP's ptyrun or ptybandage, or expect's unbuffer or using zsh's zpty builtin:

zmodload zsh/zpty
ttypager() { (zpty c "stty raw -echo; ${(q)@}"; zpty -r c) | less -RFX; }

ttypager ls

ls would run in a new session in a new terminal, so would not receive SIGINT when you press ^C, or SIGPIPE when you quit less before it finishes writing. However, since the process holding the master side (a subshell here) would still in those cases, ls would receive a SIGHUP and die nonetheless.

zpty here runs the command as if using eval, so aliases are going to be expanded. Actually, they would be expanded even if you called ttypager \ls as ttypager would still receive ls as argument and perform the alias expansion later.

Also note that both stdout and stderr would go to that pseudo-terminal and eventually to the pipe.

That's still a bit of a dirty hack. Here, the simplest would be to tell ls to behave as if it was connected to a terminal.

You could use a helper function like:

paged_ls() { ls -C --color=always "$@" | less -RFX; }

Where -C forces the output in column (which some implementations including GNU ls do by default when the output goes to a terminal) and --color=always force the output in colour regardless of whether the output goes to a terminal or not.

3

The general way to do this is with tools such as Bernstein's ptybandage:

$ ptybandage ls > wibble ; less wibble

Or, better in this case, Bernstein's ptyrun:

$ ptyrun ls | less

Further reading

JdeBP
  • 68,745