views:

475

answers:

3

First, let me state that this is a programming question (and thus does not belong on superuser et. al.) because I'm talking shell programming. This could almost be a golf question, but I do not have an answer to begin with, so any help would be appreciated :-)

So, the story is: I like to pipe stuff into less with the --quit-if-one-screen option because it is very comfortable: lessdoes not get in my way when unnecessary. Or does it ? When my prompt is already at the bottom of my terminal window, this option does exactly what I want (i.e. less behaves like cat). But, when my current prompt is at the top of the window, less first prints plenty of blank lines to clear the screen, then prints out my (short) file at the bottom of the screen, and only then it realizes that there is less text than one screen, so it exits and I get my prompt back.

But this behaviour is not great, because of all those useless blank lines. I tried different options, or wrote scripts and aliases, and the best I could come up with would be this (I'm using zsh, so the shell is already capable of duplicating pipes and so on):

function catless() {
 cat   \
  >>( bucket -$LINES | cat  ) \
  >>( bucket +$LINES | less )
}

Where bucket is another script I just wrote, which copies stdin to stdout if it is less than N lines (with -N) or more than N (with +N). I posted it here: http://snipt.net/Gyom/copy-stdin-to-stdout-or-not-depending-on-length

And ls | catless almost-works. But, for synchronization reasons, the different processes involved here do not get access to the terminal correctly and everything executes in the background (in particular, I never get a correct less here, and the prompt comes back too soon). But maybe I took the wrong path.

So, to summarize, what I want is such a function/script/whatever that I can type ls | catless and it behaves exactly like ls | cat when the output of ls is shorter than one screen, and like ls | less when longer.

Any ideas ?

+1  A: 

less has a --clear-screen option (also available as -c. You might want to check your $LESS environment variable to make sure it does not include that option. Mine does not and it behaves exactly as you want (not as you're trying to overcome).

You can disable options set in the $LESS variable on an ad hoc basis by using +. For example:

less -+c shortfile

You should also make sure that $TERM is set correctly.

If all else fails, look at the $LESSOPEN pre-processor and $LESSCLOSE post-processor features in the "INPUT PREPROCESSOR" section of man less to see if that leads you to another approach.

Dennis Williamson
first, thanks for your answer. second, dammit ! it does-work on your system ! but maybe it means there is a way.I have no $LESS variable, nor any $LESSOPEN or $LESSCLOSE, so this side is safe.about the $TERM variable, I remember that I once added a "export TERM=vt100" in my dotfiles, because with the default TERM=xterm, `less` used to display everything in a parallel universe that used to disappear from the terminal when quitting `less`. what is your $TERM ?about the +-c, it did not solve my problem. but does `less` have the same behaviour for pipes and for files ? it's not quite sure..
Gyom
argh. are there no paragraphs in comments ?
Gyom
That would be `-+c` (hyphen first), but since $LESS is null/unset it doesn't matter (try `less -c somefile` (hyphen only) just to see what it does). There are some differences with pipes (some programs when sending to pipes and less when receiving from them). My `$TERM=xterm-256color`. What I meant about `$LESSOPEN` was that you might use that feature to write a script to do what you want (if it's possible and all else fails). Also, try `less -X somefile` with `$TERM=xterm` to see if you stay out of the "parallel universe".
Dennis Williamson
yeah, I was using -+c with the hyphen first, but with no luck. it does not seem to have any effect. On the other hand, -X does indeed keep less in the current universe, so I could use it instead of my $TERM setting. I tried different combination of all those options, and could never get the behaviour I'm dreaming of (even on regular files). Maybe something to do with the way OSX's Terminal.app does its job. Anyway, thanks for trying. I'll try again with some scripting, and if I find an answer I'll post it here.
Gyom
+2  A: 

The -X flag might help you out (from less(1)):

  -X or --no-init
         Disables sending the termcap initialization and deinitialization
         strings to the terminal.   This  is  sometimes desirable if the
         deinitialization string does something unnecessary, like
         clearing the screen.

So, the following should do what you want:

export LESS="-E -X"

Or, since you like --quit-if-one-screen, you could instead:

export LESS="-F -X"
Emil
thanks. actually, my less already has all that :-) \\alias less='less --quit-if-one-screen --ignore-case --LONG-PROMPT --SILENT --RAW-CONTROL-CHARS --chop-long-lines'
Gyom
Cool. Btw, export LESS is more "portable" in the sense that if you set options via LESS in your login environment, you will automatically have it in all your shells, even if they aren't bash. And, I like "alias m=less" for less typing :-)
Emil
LESS=FX works as well, the alias l="LESS=FX less" could be used to solve the above: ls | l
+1  A: 

In the news for less version 406, I see “Don't move to bottom of screen on first page.”. Which version do you have? My system version is 382 and it moves to the bottom of the screen before printing (causing blank lines if there is only one screenful and -F is used).

I just installed version 436, and it seems to do what you want when given -FX (put it in the LESS env var with your other prefs to let anything use those prefs by just running less).

If you can not get the new version, you might try this instead:

function catless() {
    local line buffer='' num=0 limit=$LINES
    while IFS='' read -r line; do
        buffer="$buffer$line"$'\n'
        line=''
        num=$(( num+1 ))
        [[ $num -ge $limit ]] && break
    done
    if [[ $num -ge $limit ]]; then 
        { printf %s "$buffer$line"; cat } | less
    else
        printf %s "$buffer$line"
    fi
}

The key is that the shell has to know if the there are more lines in the file than the screen before it (potentially) launches less (the multi-io technique you initially used can only run things in the background). If the in-shell read is not robust enough for you, you can replace it by reworking the code a bit:

function cat_up_to_N_lines_and_exit_success_if_more() {
    # replace this with some other implmentation
    # if read -r is not robust enough
    local line buffer='' num=0 limit="$1"
    while IFS='' read -r line; do
        buffer="$buffer$line"$'\n'
        line=''
        num=$(( num+1 ))
        [[ $num -ge $limit ]] && break
    done
    printf %s "$buffer$line"
    [[ $num -ge $limit ]]
}
function catless() {
    local limit=$LINES buffer=''
    # capture first $limit lines
    # the \0 business is to guard the trailing newline
    buffer=${"$(
    cat_up_to_N_lines_and_exit_success_if_more $limit
    ec=$?
    printf '\0'
    exit $ec)"%$'\0'}
    use_pager=$?
    if [[ $use_pager -eq 0 ]]; then
        { printf '%s' "$buffer"; cat } | less
    else
        printf '%s' "$buffer"
    fi
}
Chris Johnsen
you're right ! I have another machine which happens to have less 418, and indeed, ''less -FX'' does-what-I-mean. Thank you so much ! I'll upgrade to a current ''less' on the older machine, and live happily ever after !
Gyom