4

This script outputs 5 lines with the third one being underlined:

#!/usr/bin/env bash
set -eu
bold=$(tput bold)
reset=$(tput sgr0)
underline=$(tput smul)
echo 'line 1
line 2
line 3
line 4
line 5' | awk -v bold="$bold" -v reset="$reset" -v underline="$underline" '
    NR == 3 {print underline $0 reset}
    NR != 3 {print $0}
'

If I don't reset (in the script) at the end of the third line, all the following lines are underlined, including the commands I type next (in the shell). Until I run reset. With less (./my-script.sh | less -R) not only is reset (in the script) not needed (the third line gets underlined), but it also produces extra symbol in tmux (^O, TERM=screen-256color):

line 1
line 2
line 3^O
line 4
line 5

But no symbol in plain console (TERM=xterm-256color).

What exactly and why that happens? Is there a way to make the script work in all these cases?

$ ./my-script.sh
$ ./my-script.sh | grep line --color=never
$ ./my-script.sh | less -R

E.g., to make the following script work better.

x-yuri
  • 3,223
  • 10
  • 39
  • 62
  • How are you incorporating `less`? – Jeff Schaller May 28 '18 at 18:47
  • @JeffSchaller Basically, `$ ./my-script | less -R` (`LESS=-R` is in the environment). – x-yuri May 28 '18 at 18:51
  • That’s be great to edit into the Question... – Jeff Schaller May 28 '18 at 18:55
  • What's your system, what version of `less`? I cannot reproduce either of the two issues you mentioned with less-487 as shipped by Ubuntu 18.04, or with less-527 compiled by myself.ff – egmont May 28 '18 at 19:38
  • @egmont Arch Linux, `less 530`. Try `tput sgr0 | less -R` – x-yuri May 28 '18 at 19:48
  • 1
    Oh I didn't carefully read your post, it's quite misleading: "If I don't reset (in the script) at the end of the third line [...]" so it's _not_ the script you posted as-is that produces the unexpected result?? Why not post the one that behaves unexpectedly? :) Anyway, see bug ref. number 294 at http://www.greenwoodsoftware.com/less/bugs.html. – egmont May 28 '18 at 20:07
  • As per this "issue" in `less`, if you expect to receive the same output both with and without `less -R`, you should close every color and formatting attribute at the end of every line, and reopen them in the next line if necessary. I don't know about that extra "^O" for sgr0, it's not there for me, perhaps Thomas's answer below sheds some light on it. – egmont May 28 '18 at 20:13
  • By the way, if you expect the partially underlined/bold/colored/etc. output to be piped through a `grep` (a pretty uncommon practice) and result in the same lines being underlined/bold/colored as in the unfiltered output, then again you need to open and close the attributes in each line separately, and not let any of them overflow to the next line. – egmont May 28 '18 at 20:21
  • @egmont I don't need multiline coloring. I underline only one line at a time. And with `grep`, I just want it not to leave text attributes propagate to the shell session. That is, if I don't reset at the end of the line to make `less` happy, then with `grep` underline attribute is not being reset. And I need to reset it manually after running the script. Thanks for the link by the way. And sorry for being confusing. – x-yuri May 28 '18 at 20:27

1 Answers1

3

less sends its own "reset" at the end of the line, which happens to be derived from the terminfo sgr0 by (ncurses) eliminating the ^O (reset alternate character set) because less is using the termcap interface. The termcap capability me which corresponds to terminfo sgr0 conventionally doesn't modify the alternate character set state, as noted in the manual page curs_termcap(3x):

Note that termcap has nothing analogous to terminfo's sgr string. One consequence of this is that termcap applications assume me (terminfo sgr0) does not reset the alternate character set. This implementation checks for, and modifies the data shown to the termcap interface to ac- commodate termcap's limitation in this respect.

Perhaps less is doing that reset to recover from unexpected escape sequences: the -R option is only designed to handle ANSI colors (and similarly-formatted escapes such as bold, underline, blink, standout). The source-code doesn't mention that, but the A_NORMAL assignment tells less to later emit the reset:

    /* 
     * Add a newline if necessary, 
     * and append a '\0' to the end of the line. 
     * We output a newline if we're not at the right edge of the screen, 
     * or if the terminal doesn't auto wrap, 
     * or if this is really the end of the line AND the terminal ignores 
     * a newline at the right edge. 
     * (In the last case we don't want to output a newline if the terminal  
     * doesn't ignore it since that would produce an extra blank line. 
     * But we do want to output a newline if the terminal ignores it in case
     * the next line is blank.  In that case the single newline output for 
     * that blank line would be ignored!) 
     */
    if (column < sc_width || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON) 
    {
            linebuf[curr] = '\n';
            attr[curr] = AT_NORMAL;
            curr++;
    }

As an alternative to sgr0 (which resets all video attributes, and is only partly understood by less), you could do

reset=$(tput rmul)

and (for many terminals/many systems, including TERM=screen-256color) reset just the underline. However, that does not affect bold, nor is there an conventional terminfo/termcap capability to reset bold. But screen implements the corresponding ECMA-48 sequence which does this (SGR 22 versus the 24 used in rmul), so you could hardcode that case.

Thomas Dickey
  • 75,040
  • 9
  • 171
  • 268
  • Let me complement your answer. From what I can see `"\033[m"` is hardcoded in `less`'s code (see `line.c`, `add_attr_normal()`). Which is called just above your cited block of code. `AT_NORMAL` describes the matching symbol, saying to undo attributes `less` itself has added before (not to add reset escape sequence, see `output.c`, `putline()`, `screen.c`, `at_switch()`, `at_enter()`, `at_exit()`). Allowed attributes coming from input get `AT_ANSI`. And are reset with `add_attr_normal()`. Correct me if I'm wrong. – x-yuri May 29 '18 at 04:27
  • 1
    Also, as @egmont mentioned, `less` resets at the end of the line for [performance reasons](https://unix.stackexchange.com/questions/446532/why-do-i-not-need-to-reset-text-attributes-with-less/446541#comment809677_446532): "The performance cost of implementing [multi-line coloring](http://www.greenwoodsoftware.com/less/bugs.html) is excessive. See bug 294. Do you have any suggestions how to overcome this issue? Just ignore `^O` in `less`? Or use `\033[m` instead of `$(tput sgr0)`? Any other way? – x-yuri May 29 '18 at 04:29