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.

  • 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
  • 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 greenwoodsoftware.com/less/bugs.html. – egmont May 28 '18 at 20:07

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;

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.

  • 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 4:27
  • Also, as @egmont mentioned, less resets at the end of the line for performance reasons: "The performance cost of implementing multi-line coloring 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 4:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.