#!/bin/sh ## ## much -- a pager that's even less than more(1). ## ## $Id: much,v 1.3 2001/03/19 16:44:40 dgc Exp $ ## ## Much is a replacement for more, designed for pathologically restricted ## environments such as operating system bootstraps and installers and ## "rescue" modes for recovering terminally ill systems. Much is written ## purely in true Bourne shell, requiring no extensions of ksh, bash, zsh, ## etc. It requires no external commands, other than The Shell itself, ## but some commands will add to its utility: ## ## stty & - Permit single-stroke commands, as with more. Without ## dd stty and dd, commands require an ENTER stroke as well. ## ## sed, - Permit much to size itself to your window. Without ## egrep, & sed and expr, much assumes a 24-row window, which might ## expr be fine. ## ## expr - Expr also permits pattern searching within the paged ## text. ## ## expr & - Allow much to show the percentage of a file's length ## wc which has been paged so far. ## ## I suppose that /bin/test is also required, if you're on some terribly ## old release of UNIX [tm]. Please tell me you have a test, either ## built-in or on disk. ## ## /dev/null is also required, but this is rarely a problem that one can ## work around, anyway. ## ## Much accepts input from standard input, or it accepts any number of ## files named on the command line. Options? I laff. Much doesn't ## rewind files to search backwards. Its stty test gets confused when ## text is on stdin, and it won't use raw-input mode. Not sure what to ## do here. ## ## One could add neet things with only a little work. A few tactical ## tputs for bold/italic/whatever prompts, some strategic line counts ## and exprs for displaying a percent-done indicator... not hard, but ## fluffier than I felt at all times thus far. GNU hello this ain't. ## ## OK, I take that back. I just did the word counts. ## ## Assigning the double-quote to a variable can make living with shell ## quoting rules easier. Q='"' ## We'll be mucking up $IFS. Preserve the original value. _IFS="$IFS" ## Default number of rows on screen, unless superceded by stty information ## or the $ROWS variable. $ROWSTR is a string of dots whose length is the ## value of $NROWS. This is used when expr is unavailable. NROWS=24 ROWSTR=........................ ## PSPACE is a length of whitespace used for clearing the prompt line on ## pagination. I'm just using 79 spaces since it's easiest. PSPACE=" " ## These commands return true if the indicated programs exist, and ## some false value otherwise. They should not read this program's ## stdin. That is bad. TEST_STTY="stty -g" TEST_DD="dd if=/dev/null bs=1 count=1" TEST_SED="echo | sed -e ''" TEST_EGREP="egrep . $0" TEST_EXPR="expr 1" TEST_EXPR_RX="expr foo : ." ## Regexp testing; special case TEST_WC="wc /dev/null" TEST_TPUT="tput reset /dev/tty" ## This function tests whether a command exists (using the tests defined ## above), and sets a variable to indicate the results. test_cmd () { #eval \$"TEST_$1 >/dev/null 2>&1" #echo $? #if [ $? -eq 0 ]; then if eval \$"TEST_$1 >/dev/null 2>&1"; then eval HAVE_$1=true else eval HAVE_$1=false fi } ## First, replace false with a test condition, so that we don't require ## /bin/false to exist. Not a big deal; this is probably faster, anyway. ## Do true while we're at it; it's prettier than ":". false () { test 1 -eq 0 } true () { : } ## Check all the commands we'd really like to have in our ideal world. test_cmd DD test_cmd STTY test_cmd SED test_cmd EGREP test_cmd EXPR test_cmd EXPR_RX test_cmd WC test_cmd TPUT ## Read .muchrc, and force any options there. Mainly this is for ## disabling fluff to make much marginally faster. For example, ## to really speed up everything by disabling all calculations, you ## could put ## HAVE_EXPR=false ## in ~/.muchrc. ## ## Or, to avoid displaying the percentage of the file seen so far, ## even though your system is capable of it, put ## CAN_PCENT=false ## in ~/.muchrc. ## if [ -r $HOME/.muchrc ]; then . $HOME/.muchrc fi ## Reason out our capabilities setcap () { if [ -z "`eval echo \\$$1`" ]; then _set=$1 _expr=$2 shift for _req in "$@"; do _expr="$_expr && $_req" done _expr="$_expr && $_set=true || $_set=false" eval $_expr fi } setcap CAN_KEY $HAVE_STTY $HAVE_DD setcap CAN_TSIZE $HAVE_STTY $HAVE_SED $HAVE_EGREP $HAVE_EXPR setcap CAN_RX $HAVE_EXPR_RX setcap CAN_PCENT $HAVE_EXPR $HAVE_WC ## This returns the number of rows on the screen, or else $NROWS. getrows () { if [ -n "$ROWS" ]; then echo $ROWS elif $CAN_TSIZE; then rowline=`stty -a | egrep rows` ## There are two ways I know of presenting the rows ## information. Check them both. if echo "$rowline" | egrep "rows = " >/dev/null; then sedexp='s:^.*rows = \([0-9][0-9]*\).*:\1:' elif echo "$rowline" | egrep "rows;" >/dev/null; then sedexp='s:^.* \([0-9][0-9]*\) rows;.*:\1:' else sedexp="s:.*:$NROWS:" fi echo "$rowline" | sed -e "$sedexp" else echo $NROWS fi } ## Echo without a newline. case "`echo -n`" in -n) echon=; echoc="\c";; *) echon="-n"; echoc=;; esac necho () { echo $echon "$@""$echoc" } ## Get a single character of input, if possible. Otherwise, get all the ## input up to a newline, and let the caller figure out what to do. getch () { if $CAN_KEY; then dd if=/dev/tty bs=1 count=1 2>/dev/null echo else IFS= read ch /dev/null } ## Clear the status line, and maybe replace it with something else. if $HAVE_TPUT; then st_on=`tput bold` st_off=`tput rmso` fi clstat () { necho "$PSPACE " necho $st_on"$@"$st_off } ## Perform a text search on the current stream. search () { searching=true context3= context2= context1= found=false n=0 IFS= while $searching; do IFS= read line || searching=false if $HAVE_EXPR; then n=`expr $n + 1` clstat "-- scanning... $n -- " fi IFS="$_IFS" if $CAN_RX; then if expr "$line" : ".*$1" >/dev/null 2>&1; then echo "$context3" echo "$context2" echo "$context1" echo "$line" searching=false found=true fi else case "$line" in $1) echo "$context3" echo "$context2" echo "$context1" echo "$line" searching=false found=true ;; esac fi context3="$context2" context2="$context1" context1="$line" done if $found; then Skipped=$n true else clstat "-- not found -- " false fi } ## Perform search, testing success and affecting the calling environment. ## Hooray for shell's lack of symbol scope, sort of. do_search_stuff () { if search "$pattern"; then skip=true if $HAVE_EXPR; then if [ $Skipped -ge 5 ]; then total=`expr $total + $Skipped` total=`expr $Skipped - 5` fi fi nrows=5 rowstr=.... fi } ## Paginate a file or stdin. shmore () { if [ $# -eq 0 ]; then _shmore else if $CAN_PCENT; then Lines=`wc -l <"$1"` fi _shmore <"$1" unset Lines fi } ## Paginate a single stream from stdin. _shmore () { nrows=1 # using 1 (vs. 0) leaves room for the prompt at bottom total=0 rowstr= pause=${Pause-false} remain=true skip=false ## Have to mess with IFS on reads, so that leading spaces ## are kept in the variable that's assigned to. while $remain ; do if [ $nrows -eq $ROWS -o "$rowstr" = "$ROWSTR" ]; then pause=true fi if $pause; then if $CAN_PCENT; then total=`expr $total + $nrows` fi pause=false nrows=1 rowstr= if $CAN_PCENT && [ -n "$Lines" ]; then percent=`expr $total \* 100` percent=`expr $percent / $Lines` prompt="-- much ($percent%)(h for help) --" else prompt="-- much (h for help) --" fi clstat "${Prompt-$prompt} " unset Prompt tmp=`getch` case "$tmp" in " "*) clstat ;; "") clstat pause=true ;; q*) clstat cleanup exit 0 ;; \**) clstat set | while read line; do case "$line" in HAVE_*) echo $line;; CAN_*) echo $line;; esac done pause=true skip=true ;; /) cook_tty clstat / read pattern * | \]* | N*) drain line= ;; h*|\?*) clstat help pause=true skip=true ;; esac fi if $skip; then skip=false else IFS= read line || remain=false IFS="$_IFS" echo "$line" if $HAVE_EXPR; then nrows=`expr $nrows + 1` else rowstr=.$rowstr fi fi done } ## Show help text. help () { cat <<_END_HELP_ Much -- a pager that's even less than more(1). Much is a text pager written in Bourne shell for portability and for functionality in pathologically damaged or watered-down environments. It could do more, but it already does much, and that's usually enough. q - quit now / - search [*] n - repeat search * - show abilities space - next page enter/return - next line >, ], N - next file [*] Searching uses regular expressions if expr is available and supports regexp matching. If not, searching uses shell's "case/esac" pattern rules. _END_HELP_ } ## Cook the terminal - return it to normal. cook_tty () { if $HAVE_STTY; then stty sane /dev/null fi if [ -n "$SANE_TTY" ]; then stty "$SANE_TTY" /dev/null fi } ## Rememeber the sane terminal settings for later. if $HAVE_STTY; then SANE_TTY=`stty -g /dev/null` fi ## Put the terminal into raw character-input mode. raw_tty () { if $HAVE_STTY; then stty -echo raw opost icrnl /dev/null fi } ## Clean everything up, as if we're about to quit. cleanup () { cook_tty clstat "-- none -- " } ########################################################################### trap "cleanup; exit 1" INT HUP TERM TSTP trap "raw_tty" CONT raw_tty ROWS=`getrows` if [ $# -eq 0 ]; then shmore else shmore "$1" shift for file in "$@"; do Prompt="-- many (next: $Q$1$Q)(h for help) --" Pause=true shmore "$file" done fi cleanup exit 0 ########################################################################### ## ## $Log: much,v $ ## Revision 1.3 2001/03/19 16:44:40 dgc ## Added percentages, pattern searching, and comments, and restructured ## a bunch of stuff. ## ## Revision 1.2 1999/04/16 12:59:52 dgc ## added sanity to a normal termination ## ## Revision 1.1 1999/03/09 20:14:56 dgc ## Initial revision ##