Log Watching

Very often, you have to keep an eye on a log, or maybe more than one log, and a couple of other things while a long-term simulation is running. The GNU/Linux distributions offer the program watch that allows the periodical execution of a command in the current interactive shell. While watch is convenient, you still have the problem of displaying the needed information in a terminal geometry aware way. Turns out, there are tools to query the terminal geometry and we can use them to write simple, effective, well displayed scripts.

telescope-small

So let us see how we can make BASH somewhat aware of the terminal it runs in.

The first command we will look at is stty. Traditionally, stty is used to set the various terminal’s parameters, but for now, we are interested in its parameter reporting capabilities. Invoking stty -a will output the current terminal’s settings (use the view plain option as WordPress messes up the formating a bit):

.../somewhere/>stty -a
stty -a
speed 38400 baud; rows 29; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc ixany imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke

So it seems to be reporting all needed information, including some obsolete or unneeded information (such as the terminal’s baud-rate for a terminal running on the console in graphics mode). But like most commands, it has its peculiarities. From the above, we may gather that the parameters are separated by semicolons (;) and that new lines are inserted so to more or less separate parameter categories. But if we resize the window to a very weird shape, like a very narrow, very high window, we see that the newlines change places! The first step to extract the information from stty -a is to get rid of the newlines altogether, using the tr command, replacing newlines ($'\n') by spaces:

.../somewhere/>stty -a | tr $'\n' ' '
speed 38400 baud; rows 29; columns 80; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel -iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop

Fields are fortunately separated by semicolons. Adding:

.../somewhere/>stty -a | tr $'\n' ' ' | cut -d\; -f 2-3
 rows 29; columns 80

So we’re almost done. Now, let us extract the numbers only:

.../somewhere/>stty -a | tr $'\n' ' ' | cut -d\; -f 2-3 | tr -c [0-9] ' '
      29          80

which we wrap in a BASH array:

.../somewhere/> (z=($(stty -a | tr $'\n' ' ' | cut -d\; -f 2-3 | tr -c [0-9] ' ')); echo ${z[@]} )
29 80

Using this in a script lets us get the dimensions of the terminal in characters:

#!/bin/bash

# get the rows and columns
# in the current window
#
res=($( stty -a |\
        tr $'\n' ' ' |\
        cut -d\; -f 2-3 |\
        tr -c [0-9] ' '\
    ))

cols=${res[1]}
rows=${res[0]}

Now, what we have to do is to use the known size of the terminal ($cols and $rows) for pretty-displaying a log and, say, the used disk space. Let the file watch-log contain:

#!/bin/bash

# get the rows and columns
# in the current window
#
res=($( stty -a |\
        tr $'\n' ' ' |\
        cut -d\; -f 2-3 |\
        tr -c [0-9] ' '\
    ))

cols=${res[1]}
rows=${res[0]}

# du will use du_rows rows
du_rows=$( du -h | wc -l )

# the normal log will take
# what's left (after du,
# watch, and maybe a trailing
# white line)
log_rows=$((rows-du_rows-6))

# check if the number of log rows 
# is positive, at least, and if the
# terminal is wide enough
#
if [[ $log_rows -gt 0 && $cols -ge 20 ]]
then
    echo "Space Used:"
    du -h
    echo
    echo "Log Tail:"

    # here, add your own stuff to
    # watch over
    #
    tail -n $log_rows watched.log | cut -b -${cols}
    exit 0 # success!
else
    echo "window too small for watching!"
    exit 1
fi

We invoke watch-log through watch. The actual frequency that suits you best is up to you, but to get an update every second, you would invoke: watch -n 1 watch-log. Now, resizing the window produces a seamless update of the information.

*
* *

Working with cut, sed and tr is alien at first, but they will prove most useful in basic or advanced scripting. Here, we used two of them to extract and use the terminal’s resolution (in characters). Another thing that we could have done to beautify the script is to use colors, but I personally think they’re best used in moderation. Maybe we could display the result of du in red should the used disk space exceed, say, 1GB or some other convenient limit, but I will leave that to you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: