At the top of my 'bio' page at uniquename, I have mentioned a set of 'Freedom Environment' utilities --- many of which are implemented with Tcl-Tk --- that are available from www.freedomenv.com.

Also on that 'bio' page, I committed to contributing to this wiki five 'QuikPlot' utilities, adapted into 'stand-alone' scripts taken from scripts integrated into one of the FE subsystems. I have posted two of those plot utilities, so far.

In addition to the plot utilities, there is one other 'Freedom Environment' utility that I would like to contribute to these pages --- a text file browser utility that I call 'xpg'.

The 'xpg' utility has a powerful 'Show-All-Match-Lines' feature that I have used over and over in helping me write the 35-plus Tk scripts that I have donated (so far) to these wiki pages.

One reason I would like to donate this utility to these pages is because I get tired of reading web postings claiming that Tcl-Tk is dead. I think this 'xpg' utility, that I use almost daily, shows that Tcl-Tk is not dead. It lives every day --- at least for me.

The features of the 'xpg' utility have evolved over multiple years, but I have been using it several years now with no major enhancements. So I think it is appropriate to donate the code to this wiki at this time.

Here is an image that demonstrates what the GUI looks like.

In this image, the 'xpg' utility is showing the text file 'shofil.tk', which is the Tk code that is showing this GUI.

You can see that the GUI includes the typical 'Exit' and 'Help' buttons, as well as a 'Search string' entry field that is typical of applications that browse text files.

The really powerful feature of this 'xpg' utility comes from the 'SAM' (Show All Matches) button on the GUI.

If the user clicks on the SAM button, instead of the Search button, the 'xpg' utility uses itself to show a listing of match lines in a new popup window.

In fact, the 'xpg' utility uses itself to show the Help file that is shown when the user clicks on the 'Help' button.

Another couple of buttons that need a little explaining are the '<-Ybar->' button and the 'OtherOpts' button.

The '<-Ybar->' button toggles the vertical scrollbar back-and-forth, from the right side of the GUI to the left, and back. I use this quite frequently, because I often like to have the scrollbar on the left --- as I am editing a Tk script in the 'Scite' text editor, while having some sample code in 'xpg', for reference.

The 'OtherOpts' button shows a 'hidden' frame of additional search options (and a 'WinColor' button and a 'TxtFont' button) --- just below the 'Search(again)' and 'SAM' buttons.

The 'OtherOpts' frame can be seen in a screenshot that is posted below the code on this page.

Both the '<-Ybar->' button and the 'OtherOpts' button are implemented by use of the Tk 'pack forget' command.

One other feature of the GUI that is worth pointing out at this point is the '+/-' label on the GUI, just to the right of the 'SAM' button. The number 0 appears just to the right of that string, and there is a popout menu there that allows the user to change the number.

That number allows the user to request that NOT JUST THE MATCH LINES be shown when the 'SAM' button is clicked, but ALSO N-lines above and below each match line.

This can be handy when one is, for example, looking for info on an issue in a large README file, and one needs to see the lines above and below match lines in order to see the lines that are needed to fully describe the issue -- the complete sentences containing the search-word(s).

The code

Below, I provide the Tk script code for this 'xpg' utility.

This utility is actually composed of about 8 files (four shell scripts, 3 Tk scripts, and a help file) --- but the central guts of the utility is a Tk script that I call 'shofil.tk'.

I will post the code for the 4 shell scripts below the code for 'shofil.tk', below.

The 2 'find' shell scripts use the 'awk' command to perform their function. And the other 2 shell scripts use quite standard Linux/Unix/BSD commands to do their work. So ALL of this contributed code should work on most Linux, Unix, and BSD systems. If there are small differences in the way the 'awk' command works on a particular system, you can probably get the 2 'find' scripts to work by making no more than a few minor changes.

And since Apple Mac operating systems are based on BSD systems, it is quite likely that this set of scripts will work on Mac's --- but I have no Mac machine on which to try this (nor do I want to spend the time to do such testing --- I have too many other Tcl-Tk projects to address).

But if anyone tries out this set of code on a Mac, I would be interested to hear whether it all worked out.

In relation to Microsoft operating systems, the 'shofil.tk' Tk script --- and the color-selector and font-selector Tk scripts --- should probably work with few changes. However, the 'SAM' button, which uses the 2 'find' scripts, will not work unless the user has a package installed that includes the 'awk' program --- and even then some changes to the scripts will probably have to be made.

Another alternative would be to rewrite the 2 'find' scripts in C code, and replace the 2 find scripts with compiled C code. (I leave it to others to handle porting these files to a Microsoft OS.)

I followed my usual 'canonical' structure for Tk code in writing the 'shofil.tk' script:

0) Set general window & widget parms (win-name, win-position,
win-color-scheme, fonts, widget-geometry-parms, win-size-control,
text-array-for-labels-etc).
1a) Define ALL frames (and sub-frames, if any).
1b) Pack ALL frames and sub-frames.
2) Define & pack all widgets in the frames, frame by frame.
Within each frame, define ALL the widgets.
Then pack the widgets.
3) Define keyboard and mouse/touchpad/touch-sensitive-screen action
BINDINGS, if needed.
4) Define PROCS, if needed.
5) Additional GUI initialization (typically with one or more of
the procs), if needed.

This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in other scripts (code re-use).

I call your attention to step-zero. One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.

Experimenting with the GUI

As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', '-expand' --- on all of the 'pack' commands for the frames and widgets.

That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window.

I think that I have used a nice choice of the 'pack' parameters for the 'shofil.tk' GUI. The labels and buttons and checkbuttons stay fixed in size and relative-location as the window is re-sized --- while the entry field x-exands as the window x-expands --- and the 'text' widget and its x and y scrollbars expand/contract appropriately as the window size is changed.

You can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various frames and widgets --- to get the widget behavior that you want.

___

In addition, you might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families.

In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.

I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

Some features in the code

That said, here's the code for 'shofil.tk' --- with plenty of comments to describe what most of the code-sections are doing.

You can look at the top of the PROCS section of the code to see a list of the procs used in this script, along with brief descriptions of how they are called and what they do.

The two main procs that do the search functions are 'search4string' and 'all_matches2string'. The second of these implements the 'SAM' button.

Another main proc is 'getfile2textwidget' which uses the Tcl 'gets' command to load lines from the file into the 'text' widget.

It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this.

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of friends bashing friends in the face. (I wonder how many are still friends?)

Here is a screenshot that shows the frame revealed by clicking on the 'OtherOpts' button.

For this image, I used the 'WinColor' button to bring up the color-selector GUI and set the window color to a brown that sort of matches the brown shades that were common to the Ubuntu Linux desktop in the 2008-2009 era.

Here is code for the 4 shell scripts of the 'xpg' system. (Perhaps these code segments should be posted on a separate page, since this page is quite long already.)

Code for the 'findANDshow_stringsINfile_plusminusNlines.sh' script:

#!/bin/sh
##
## SCRIPT: findANDshow_stringsINfile_plusminusNlines.sh
##
## adapted from the 'FE xpg' subsystem of the Freedom Environment system,
## whose home page is at www.freedomenv.com.
##
## This is a 'stand-alone' version for contribution to wiki.tcl.tk
##
## PURPOSE: Let $1 $2 $3 $4 represent the 4 positional arguments to this script
## --- <string> <integer> <yes/no> <filename>.
##
## For the filename specified in $4, this script finds the lines in
## the file which contains a match to the string in $1 ---
## and it shows N lines above and below the 'match' lines, where
## N=$2. It uses case-sensitivity in the search, depending on
## whether $3 is 'yes' or 'no'.
##
## Like 'egrep', the string argument may be sub-strings separated
## by vertical-bars (|). Hence, the file can actually be searched
## for an OR-match, in any record of the file, to the several
## sub-strings within the string.
##
## Example string: 'fatal|error|fail|warning'
##
## NOTE: INCLUDING THE SINGLE QUOTES. They are needed to keep the
## shell from trying to execute the string, esp. when it
## contains special characters like < or > or [ or ] or
## parentheses or ! or whatever.
##
## 'awk' is used to find the match(es) and save-and-print the N lines
## above a match line as well as the N lines following, where N = $2.
##
## This is an 'egrep-like' utility --- except that it is an extended
## 'egrep' (one might say an 'eegrep') utility that shows lines
## around the match lines. This script does this with 'awk',
## because 'egrep' is not capable of showing the nearby lines.
## CALL FORMAT:
##
## $DIRxpg/findANDshow_stringsINfile_plusminusNlines.sh \
## <string> <integer> <yes/no> <filename>
##
## CALLED BY: the tk-GUI utility script 'shofil.tk'
##
## to support the unique 'Show All Matches' productivity feature.
##
## MAINTENANCE HISTORY for this 'stand-alone' version:
##
## Written: Blaise Montandon 2013aug05 Started work on creating this
## 'stand-alone' version from the
## 'integrated' 'FE xpg' subsystem at
## www.freedomenv.com.
## Updated: Blaise Montandon 20.......
THISscript="$0"
THISscriptBASE=`basename "$THISscript"`
## Save input, for err msg below.
ALLinput="$*"
## FOR TESTING:
# echo "
# ALLinput: $ALLinput"
## Get input items #1 and #2 and #3 and #4 from one string of input.
## Based on the following '$1' and 'shift' example,
## from /apps/ideas_9/bin/msplot,
## which preserves any arguments in quotations:
##
## while test "$1" != ""
## APP_ARGS="$APP_ARGS \"$1\""
## shift
## done
## 1) Get the first of 4 parms --- the <string> parm.
## Use 'eval' to remove single-quotes that protect the string from
## interpretation by the shell. Now the string can be special
## characters like '>'.
# eval STRINGin=$1
## seems to work OK. But let's try double-quotes around $1.
eval STRINGin="$1"
## Change "\" to "\\" --- to avoid awk err msg
## 'Newline in string ... at source line 1'
if test "$STRINGin" = '\'
then
STRINGin='\\'
fi
## FOR TESTING:
# echo "
# STRINGin: $STRINGin"
shift
Nlines="$1"
## FOR TESTING:
# echo "
# Nlines: $Nlines"
shift
CaseSense="$1"
## FOR TESTING:
# echo "
# CaseSense: $CaseSense"
shift
FILEin="$1"
## FOR TESTING:
# echo "
# FILEin: $FILEin"
## Check for input item #1 and #2 and #3 and #4.
ERRMSG_MAIN="\
***********
INPUT ERROR:
Supply 4 inputs --- a STRING and an INTEGER (Nlines) and
yes/no (CaseSense) and a FILENAME
to script
$THISscript.
INPUT FORMAT: <STRING> <INTEGER> <yes/no> <FILENAME>
EXAMPLES:
$THISscriptBASE 'error' 3 no /var/adm/SYSLOG
$THISscriptBASE 'error|warning|core|dump' 3 no /var/adm/SYSLOG
$THISscriptBASE 'file systems' 3 yes /var/adm/SYSLOG
CURRENT INPUT: $ALLinput
"
if test "$STRINGin" = ""
then
echo "
$ERRMSG_MAIN
Supply a (search) STRING to script
$THISscript.
Exiting ...
"
exit
fi
if test "$Nlines" = ""
then
echo "
$ERRMSG_MAIN
Supply an INTEGER (plus-minus-Num-Lines) to script
$THISscript.
Exiting ...
"
exit
fi
if test "$CaseSense" = ""
then
echo "
$ERRMSG_MAIN
Supply a Case-Sensitivity Indicator (yes/no) to script
$THISscript.
Exiting ...
"
exit
fi
if test "$FILEin" = ""
then
echo "
$ERRMSG_MAIN
Supply the FILENAME (of the file to search) to script
$THISscript.
Exiting ...
"
exit
fi
## Set DIRxpg for use in the 'stand-alone' version of the 'xpg' system.
# DIRxpg="$HOME/apps/xpg"
DIRxpg="."
## PREPARE A REPORT FILE --- and its HEADING.
## (Put the lines in a local file whose name is built in $OUTLIST,
## by the 'set_localoutlist' utility.)
## This dot (.) is not needed. It just seems to be a little more efficient
## to not spawn off a new shell environment to run this script, if we
## do not need to do so.
. $DIRxpg/set_localoutlist.sh
CaseSenseMsg="( CASE-SENSITIVE! )"
if test "$CaseSense" = "no"
then
CaseSenseMsg="(upper or lower case ; that is, CASE-INSENSITIVE)"
fi
MULTICHECK=`echo "$STRINGin" | grep '|'`
if test "$MULTICHECK" = ""
then
StringMsg="STRING"
else
StringMsg="STRINGS separated by '|' in "
fi
echo "\
********************* `date '+%Y %b %d %a %T%p %Z'` ******************
'MATCH' LINES FROM FILE
$FILEin
Lines that contain the $StringMsg '$STRINGin'
$CaseSenseMsg
--- INCLUDING $Nlines line(s) ABOVE-AND-BELOW 'match' lines.
All lines are preceded by line numbers.
An asterisk (*) before a line-number indicates a match,
to the string, was found in the line.
LineNumber:Text
----------------------------------------------------------------------------
" > "$OUTLIST"
## CALL 'awk' -- with an appropriate awk program -- with a file as input.
## 'awk' program (AN EXAMPLE) to
## write all lines whose first field is different from the previous one.
##
## $1 != prev { print; prev = $1 }
## NOTE:
## This extended-egrep 'eegrep' script is basically
## a more complex version of this example.
## FOR TESTING:
# TEST="YES"
TEST="NO"
if test "$TEST" = "YES"
then
echo "
*..........................................................
* Lines in file $FILEin
* that match the '|'-separated sub-strings in '$STRINGin'
* --- including $Nlines line(s) above-and-below matches
* --- are shown below.
*..........................................................
* All lines are preceded by line numbers.
* An asterisk (*) before a line-number indicates a match.
*..........................................................
"
# set -x
fi
## HERE's the 'awk'.
## Add 'cut -c1-3071 $FILEin |' before
## awk, to avoid 'Input record too long'
## error that stops awk dead.
cut -c1-3071 "$FILEin" | \
awk -v N="$Nlines" -v STRING="$STRINGin" -v CASESENSE="$CaseSense" \
'BEGIN {
#######################################################
## Initialize the N "prev" vars to null.
## They are to hold the last N lines read.
#######################################################
for ( i = 1 ; i <= N ; i++ )
{
prev[i] = ""
}
##################################################
## After converting STRING to upper-case,
## if CASESENSE=no,
## split the "STRING" into NS "subSTRING"s -- at
## occurrences of a vertical bar (|).
##################################################
if ( CASESENSE == "no" ) {
STRING = toupper(STRING)
}
NS=split(STRING,subSTRING,"|")
## FOR TESTING:
# print "CASESENSE: " CASESENSE
# print "NS: " NS
# print "subSTRING[1] :" subSTRING[1]
# print "subSTRING[2] :" subSTRING[2]
# print "subSTRING[3] :" subSTRING[3]
###################################################
## "aftcount" holds the integer N,N-1,...,2,1, or 0
## --- representing the number of lines after the
## last matched line that still need to be printed.
###################################################
aftcount = 0
######################################################
## "lastprt" holds the line# of the line last printed.
## "lastprt" is reset any time "printf" is called.
######################################################
lastprt = 0
}
#END OF BEGIN
#START OF BODY
{
####################################################
## IF WE HAVE A MATCH, SUSPEND PRINTING
## at N "AFTER-A-MATCH-LINES":
## If there is a new match, reset "aftcount" to zero.
## (We do not want to print a line twice.)
## We will restart aftcount at N after the new match
## line is printed.
####################################################
## We use "Match" to indicate whether there was a
## match to at least one of the subSTRINGs, in the
## current line ($0). Match==1 indicates a match.
####################################################
Match = 0
if ( CASESENSE == "no" ) {
HOLDline = toupper($0)
}
if ( CASESENSE == "yes" ) {
HOLDline = $0
}
## FOR TESTING:
# if ( NR < 10 ) { print "HOLDline :" $HOLDline }
for ( i = 1 ; i <= NS ; i++ ) {
## This fails when certain special chars are in the substring.
# if ( HOLDline ~ subSTRING[i] ) { aftcount = 0 ; Match = 1 }
if ( index(HOLDline,subSTRING[i]) != 0 ) { aftcount = 0 ; Match = 1 }
## FOR TESTING:
# print ""
# print "HOLDline: " HOLDline
# print "subSTRING LOOP - i: " i " subSTRING[i]: " subSTRING[i] " aftcount: " aftcount " Match: " Match
# print "index(HOLDline,subSTRING[i]): "index(HOLDline,subSTRING[i])
}
## FOR TESTING:
# }" "$FILEin"
# exit
######################################################
## PRINT ONE OF THE N "AFTER-A-MATCH-LINES":
## If "aftcount" is non-zero, print the current line.
## We had a match up to N lines ago. Decrement "aftcount"
## and save the number of the printed line in "lastprt".
######################################################
if ( aftcount != 0 ) {
printf (" %s : %s \n", NR, $0);
## If this is the last of the "aftcount" lines,
## print a blank line.
if ( aftcount == 1 ) {print ""}
aftcount = aftcount - 1 ;
lastprt = NR
## FOR TESTING:
# print "aftcount != 0 CHECK:: aftcount: " aftcount " lastprt: " lastprt
}
## FOR TESTING:
# }" "$FILEin"
# exit
######################################################
## IF WE HAVE A MATCH, PRINT N-PREV & CURRENT:
## If there is a match, print the N previous lines
## --- as long as their linenums are greater than
## the last-printed line number. (We do not want
## to print a line twice.)
##
## Then print the current line. Also set "aftcount"
## to N, and save the
## number of the matched-printed line in "lastprt".
######################################################
for ( i = N ; i > 0 ; i-- ) {
recnum = NR - i
if ( Match == 1 && recnum > lastprt ) {
printf (" %s : %s \n", recnum, prev[i])
}
## FOR TESTING:
# print "prev[] PRINT-LOOP:: NR= " NR " recnum= " recnum " i= " i
# print "prev[] PRINT-LOOP:: lastprt= " lastprt " prev[i]= " prev[i]
}
if ( Match == 1 ) {
printf ("*%s : %s \n", NR, $0);
aftcount = N;
lastprt = NR
## FOR TESTING:
# print "Match == 1 TEST:: aftcount: " aftcount " lastprt: " lastprt
}
########################################################
## Update prev[N], prev[N-1], ... , prev[2], and prev[1]
## before reading the next line.
########################################################
for ( i = N ; i > 1 ; i-- )
{
prev[i] = prev[i-1]
}
prev[1] = $0
#END OF BODY
}' >> "$OUTLIST"
## WAS:
## }' "$FILEin" >> "$OUTLIST"
## ADD A TRAILER TO THE REPORT-FILE.
echo "
----------------------------------------------------------------------------
The report above was created by the utility script
$THISscriptBASE
The script uses an 'awk' program that essentially extends the
capabilities of the 'egrep' (extended grep) program. ['grep' is a program
that can find lines in a file that contain a given string of characters.]
'egrep' can show the lines of a file that contain matches to *one-or-more*
strings. Example: 'error', 'fail', 'fatal', or 'warning'.
With 'egrep', the multiple-strings argument is formed by separating the
multiple strings by vertical-bars (|). Example: 'fatal|error|fail|warning'
But 'egrep' cannot show nearby lines. The 'awk' program used here essentially
creates an extension of the 'egrep' (extended grep) utility.
As the name (above) of the utility script implies, this utility can show
plus-or-minus N lines above and below the lines that have a match for
the search string(s).
You could say this is an 'eegrep' utility --- extended, extended grep.
----------------------------------------------------------------------------
With 'egrep', one can make the search case-insenstive with the '-i' option.
Likewise, this '$THISscriptBASE' utility
can be told to make the search either case-insensitive or case-sensitive.
As indicated above, like 'egrep', this '$THISscriptBASE'
utility accepts the '|' character. Then, to find all lines containing
either 'memory' or 'RAM' or 'disk', you can use the search string
'memory|ram|disk' --- with case-sensitivity switch OFF.
If that returns too many lines with the string 'ram' --- lines with words
like 'datagram' or 'telegram' or 'ramble' or 'gram' --- then switch the
case-sensitivity switch to ON --- and use a string like 'memory|RAM|disk'.
----------------------------------------------------------------------------
The script
$THISscriptBASE
is called from within the FE system 'xpg' text-file
browse/extract/print utility.
[Actually, the 'xpg' script calls the Tcl-Tk GUI script
$DIRxpg/shofil.tk
--- and it is this Tcl-Tk script that calls the script
$THISscriptBASE.]
---------------------------------------------------------------------------
The 'Extract-String-Match-Lines' capability of the 'xpg' utility is
very useful --- especially to people who deal with huge files such as
system log files and huge lists (say of files) and large README-like help files
--- people like system administrators and application developers and engineers.
---------------------------------------------------------------------------
If 'xpg' is 'not found' when you type 'xpg' at a shell command prompt,
you can make an alias for 'xpg' by putting the following alias definition
in your shell 'rc' (run control) file --- such as $HOME/.bashrc or
$HOME/.bash_aliases.
alias xpg='$HOME/apps/bin/xpg'
Then logoff-logon to make the alias available in every window of a
login session.
---
You could also make a desktop icon for the
$HOME/apps/bin/xpg utility,
and drop text files on it.
---
If you type 'xpg', without an input filename, at a command prompt, you
will see a usage hint like the following.
Usage: xpg [-h] [-f] file1 [ file2 ... file8 ]
I.e. xpg is setup to browse up to 8 files at a time. The limit is to help
avoid accidentally opening up more xpg-windows than one wants to deal with.
This can happen if you have an 'xpg' icon on your desktop and you
unintentionally drag a sheet-load of filenames onto the icon.
********************* `date '+%Y %b %d %a %T%p %Z'` ******************
" >> "$OUTLIST"
## SHOW THE REPORT-FILE OF ERR LINES FROM THE SELECTED PRINT FILE, $FILEIN.
$DIRxpg/xpg "$OUTLIST"

Code for the 'findANDshow_NOTstringsINfile.sh' script:

#!/bin/sh
##
## SCRIPT: findANDshow_NOTstringsINfile.sh
##
## adapted from the 'FE xpg' subsystem of the Freedom Environment system,
## whose home page is at www.freedomenv.com.
##
## This is a 'stand-alone' version for contribution to wiki.tcl.tk
##
## PURPOSE: Let $1 $2 $3 represent the 3 positional arguments to this script --
## <string> <yes/no> <filename>.
##
## For the filename specified in $3, this script finds the lines in
## the file which DO NOT contain a match to the string(s) in $1.
## It uses a case-sensitive search, depending on whether $2 is
## 'yes' or 'no'.
##
## Like 'egrep', the string argument may be sub-strings separated
## by vertical-bars (|). Hence, the file can actually be searched
## for a NOT-AND-match, in any record of the file, to the several
## sub-strings within the string.
##
## Example string: 'fatal|error|fail|warning'
##
## NOTE: INCLUDING THE SINGLE QUOTES. They are needed to keep the
## shell from trying to execute the string, esp. when it
## contains special characters like < or > or [ or ] or
## parentheses or ! or whatever.
##
## 'awk' is used to find the NON-match(es) lines of the input file.
##
## This is an 'egrep-like' utility. It is like 'egrep -v' or
## 'egrep -i -v'.
##
## However, by using 'awk', we might extend this search someday to
## accept an Nlines integer input --- and then show plus-or-minus N lines
## around the NON-match lines. This script would do this with 'awk',
## because 'egrep' is not capable of showing the nearby lines.
## CALL FORMAT:
##
## $DIRxpg/findANDshow_NOTstringsINfile.sh <string> <yes/no> <filename>
##
## CALLED BY: the tk-GUI utility script 'shofil.tk'
##
## to support the unique 'Show All Matches' productivity feature.
##
## MAINTENANCE HISTORY for this 'stand-alone' version:
##
## Written: Blaise Montandon 2013aug05 Started work on creating this
## 'stand-alone' version from the
## 'integrated' 'FE xpg' subsystem at
## www.freedomenv.com.
## Updated: Blaise Montandon 20.......
THISscript="$0"
THISscriptBASE=`basename "$THISscript"`
## Save input, for err msg below.
ALLinput="$*"
## FOR TESTING:
# echo "
# ALLinput: $ALLinput"
## Get input items #1 and #2 and #3 from one string of input.
## Based on the following '$1' and 'shift' example,
## which preserves any arguments in quotations:
##
## while test "$1" != ""
## APP_ARGS="$APP_ARGS \"$1\""
## shift
## done
## 1) Get the first of 3 parms --- the <string> parm.
## Use 'eval' to remove single-quotes that protect the string from
## interpretation by the shell. Now the string can be special
## characters like '>'.
# eval STRINGin=$1
## seems to work OK. But let's try double-quotes around $1.
eval STRINGin="$1"
## Change "\" to "\\" --- to avoid awk err msg
## 'Newline in string ... at source line 1'
if test "$STRINGin" = '\'
then
STRINGin='\\'
fi
## FOR TESTING:
# echo "
# STRINGin: $STRINGin"
shift
CaseSense="$1"
## FOR TESTING:
# echo "
# CaseSense: $CaseSense"
shift
FILEin="$1"
## FOR TESTING:
# echo "
# FILEin: $FILEin"
## Check for input item #1 and #2 and #3.
ERRMSG_MAIN="\
***********
INPUT ERROR:
Supply 3 inputs --- a STRING and yes/no (CaseSense) and a FILENAME
to script
$THISscript.
INPUT FORMAT: <STRING> <yes/no> <FILENAME>
EXAMPLES:
$THISscriptBASE 'error' no /var/adm/SYSLOG
$THISscriptBASE 'error|warning|core|dump' no /var/adm/SYSLOG
$THISscriptBASE 'file systems' yes /var/adm/SYSLOG
CURRENT INPUT: $ALLinput
"
if test "$STRINGin" = ""
then
echo "
$ERRMSG_MAIN
Supply a (search) STRING to script
$THISscript.
Exiting ...
"
exit
fi
if test "$CaseSense" = ""
then
echo "
$ERRMSG_MAIN
Supply a Case-Sensitivity Indicator (yes/no) to script
$THISscript.
Exiting ...
"
exit
fi
if test "$FILEin" = ""
then
echo "
$ERRMSG_MAIN
Supply the FILENAME (of the file to search) to script
$THISscript.
Exiting ...
"
exit
fi
## Set DIRxpg for use in the 'stand-alone' version of the 'xpg' system.
# DIRxpg="$HOME/apps/xpg"
DIRxpg="."
## PREPARE A REPORT FILE --- and its HEADING.
## (Put the lines in a local file whose name is built in $OUTLIST,
## by the 'set_localoutlist' utility.)
## This dot (.) is not needed. It just seem to be a little more efficient
## to not spawn off a new shell environment to run this script, if we
## do not need to do so.
. $DIRxpg/set_localoutlist.sh
CaseSenseMsg="(CASE-SENSITIVE)"
if test "$CaseSense" = "no"
then
CaseSenseMsg="(upper or lower case ; that is, CASE-INSENSITIVE)"
fi
MULTICHECK=`echo "$STRINGin" | grep '|'`
if test "$MULTICHECK" = ""
then
StringMsg="string"
else
StringMsg="any of the strings separated by '|' in "
fi
echo "\
********************* `date '+%Y %b %d %a %T%p %Z'` ******************
'NON-MATCH' LINES FROM FILE
$FILEin
Lines that DO NOT contain $StringMsg '$STRINGin'
$CaseSenseMsg
All lines are preceded by line numbers.
LineNumber:Text
----------------------------------------------------------------------------
" > "$OUTLIST"
## CALL 'awk' -- with an appropriate awk program -- with a file as input.
## 'awk' program (AN EXAMPLE) to
## write all lines whose first field is different from the previous one.
##
## $1 != prev { print; prev = $1 }
## NOTE:
## This extended-egrep 'eegrep' script is basically
## a more complex version of this example.
## FOR TESTING:
# TEST="YES"
TEST="NO"
if test "$TEST" = "YES"
then
echo "
*..........................................................
* Lines in file $FILEin
* that DO NOT match ANY of the '|'-separated sub-strings in
* the string
* '$STRINGin'
* are shown below.
*..........................................................
"
# set -x
fi
## HERE's the 'awk'.
## Add 'cut -c1-3071 $FILEin |' before
## awk, to avoid 'Input record too long'
## error that stops awk dead (on SGI-IRIX).
cut -c1-3071 "$FILEin" | \
awk -v STRING="$STRINGin" -v CASESENSE="$CaseSense" \
'BEGIN {
##################################################
## After converting STRING to upper-case,
## if CASESENSE=no,
## split the "STRING" into NS "subSTRING"s -- at
## occurrences of a vertical bar (|).
##################################################
if ( CASESENSE == "no" ) {
STRING = toupper(STRING)
}
NS=split(STRING,subSTRING,"|")
## FOR TESTING:
# print "CASESENSE: " CASESENSE
# print "NS: " NS
# print "subSTRING[1] :" subSTRING[1]
# print "subSTRING[2] :" subSTRING[2]
# print "subSTRING[3] :" subSTRING[3]
######################################################
## "lastprt" holds the line# of the line last printed.
## "lastprt" is reset any time "printf" is called.
######################################################
lastprt = 0
}
#END OF BEGIN
#START OF BODY
{
if ( CASESENSE == "no" ) {
HOLDline = toupper($0)
}
else {
HOLDline = $0
}
## FOR TESTING:
# if ( NR < 10 ) { print "HOLDline :" $HOLDline }
####################################################
## We use "Match" to indicate whether there was a
## match to the subSTRINGs, in the current
## line ($0). Match==1 indicates a match to at least
## ONE the substrings, which means we do NOT print
## the line. We print the line if Match==0.
####################################################
Match = 0
for ( i = 1 ; i <= NS ; i++ ) {
if ( index(HOLDline,subSTRING[i]) != 0 ) { Match = 1 }
## FOR TESTING:
# print "subSTRING LOOP - subSTRING[i]: " subSTRING[i] " Match: " Match
#
# print "index(HOLDline,subSTRING[i]): "index(HOLDline,subSTRING[i])
}
## FOR TESTING:
# }" "$FILEin"
# exit
######################################################
## PRINT THE LINE if Match == 0 (none of the substrings
## had a match in the line).
######################################################
if ( Match == 0 ) {
printf (" %s : %s \n", NR, $0);
lastprt = NR
## FOR TESTING:
# print "Match == 0 --- lastprt: " lastprt
}
## FOR TESTING:
# }" "$FILEin"
# exit
#END OF BODY
}' >> "$OUTLIST"
## }' $FILEin >> "$OUTLIST"
## ADD A TRAILER TO THE REPORT-FILE.
echo "
----------------------------------------------------------------------------
The report above was created by the utility script
$THISscriptBASE
The script uses an 'awk' program that shows all lines of an input file
EXCEPT the lines which contain a match to at least one of *one-or-more*
strings. Example: 'error', 'fail', 'fatal', or 'warning'.
A multiple-strings argument is formed by separating the
multiple strings by vertical-bars (|). Example: 'fatal|error|fail|warning'
----------------------------------------------------------------------------
As an example of how this 'NOT' search capability works, if you use the
string '##' and search one of the scripts in any of the FE subsystems,
you will see the script with most of the comment lines removed.
----------------------------------------------------------------------------
You can eliminate more comment lines by using the search string '##|# '
where there is a space character after the #-sign in the 2nd string.
(The space character is helpful to avoid eliminating lines with
a string like '#c0c0ff' which could be a hex string specifying
a color for a GUI.)
----------------------------------------------------------------------------
And you can use the 'NOT' search capability following a search without 'NOT'.
For example, if you want to see all the lines of a script that contain the
string 'HOME', you could do a search without 'NOT'. Then, if that returned
so many comment lines that the extraction-list was hard to read, apply a
'NOT' search, on that extraction list which is being shown in an 'xpg' window,
using the string '##' . In the resulting 3rd 'xpg' window, you will find
an extraction-list with mostly non-comment lines that contain the string 'HOME'.
Alternatively, you can do the 'NOT' search FIRST, to get rid of most of
the comment lines. And THEN do an 'xpg' search with string 'HOME', with the
'NOT' checkbutton turned OFF.
----------------------------------------------------------------------------
The script $THISscriptBASE
is called from within the FE system 'xpg' text-file
browse/extract/print utility.
[Actually, the 'xpg' script calls the Tcl-Tk GUI script
$DIRxpg/shofil.tk
--- and it is this Tcl-Tk script that calls the script
$THISscriptBASE.]
---------------------------------------------------------------------------
If 'xpg' is 'not found' when you type 'xpg' at a shell command prompt,
you can make an alias for 'xpg' by putting the following alias definition
in your shell rc (run control) file --- such as $HOME/.bashrc or
$HOME/.bash_aliases.
alias xpg='$HOME/apps/bin/xpg'
Then logoff-logon to make the alias available in every window of a
login session.
---
You could also make a desktop icon for the
$HOME/apps/bin/xpg utility,
and drop text files on it.
---
If you type 'xpg', without an input filename, at a command prompt, you
will see a usage hint like the following.
Usage: xpg [-h] [-f] file1 [ file2 ... file8 ]
I.e. xpg is setup to browse up to 8 files at a time. The limit is to help
avoid accidentally opening up more xpg-windows than one wants to deal with.
This can happen if you have an 'xpg' icon on your desktop and you
unintentionally drag a sheet-load of filenames onto the icon.
********************* `date '+%Y %b %d %a %T%p %Z'` ******************
" >> "$OUTLIST"
## SHOW THE REPORT-FILE OF ERR LINES FROM THE SELECTED PRINT FILE, $FILEIN.
$DIRxpg/xpg "$OUTLIST"

Code for the 'set_localoutlist.sh' script:

#!/bin/sh
##
## SCRIPT NAME: set_localoutlist.sh
##
## Where: in $FE_SCRIPTS_DIR=$FEDIR/scripts
##
## where $FEDIR represents the install directory of an FE subsystem.
##
## PURPOSE:
## Sets OUTLIST="/tmp/$USER/temp.lis####", if the /tmp/$USER directory
## exists.
## Otherwise, it sets OUTLIST="$HOME/tmp/temp.lis####".
##
## Here #### represents 4 integers generated near-randomly from the
## minutes & seconds in the time of day.
##
## In addition, to avoid accumulation to 'temp.lis' files, there are
## two sections at the bottom of the script to remove
##
## a) all but the N most-recent 'temp.lis####...' files, where N is
## an integer like 50 or 100.
## b) the big 'temp.lis####...' files, where 'big' is say > 50Meg.
## TYPICAL CALL FORMAT: . $FEDIR/scripts/set_localoutlist.sh
##
## WHERE USED:
## 'set_localoutlist.sh' is used by FE subsystem scripts, like the
## 'findANDshow_stringINfile_plusminusNlines.sh'
## script of the FE 'xpg' subsystem.
##
## You can use the command
## grep 'set_localoutlist' *
## in a $FEDIR/scripts directory to show scripts that use
## 'set_localoutlist.sh'.
##
## MAINTENANCE HISTORY:
## Updated by: Blaise Montandon 2008mar24 Started the 'set_localoutlist.sh'
## script on Linux, using Mandriva2007.
## Updated by: Blaise Montandon 2009nov11 Restarted development, on Ubuntu.
## Found that 'tail +N' does not work
## on Ubuntu Karmic (9.10).
## Used a 'tail -N' technique to get
## all but the first 100 recs in a
## time-sorted list of the temp files.
## Updated by: Blaise Montandon 2010sep06 Touched up comments for an initial
## release of the FE 'xpg' subsystem.
TEMPDIR="/tmp"
## MAKE $USER SUBDIRECTORY of $TEMPDIR, if it does not exist.
if test ! -d $TEMPDIR/$USER
then
mkdir $TEMPDIR/$USER
chmod 777 $TEMPDIR/$USER
## We want to allow other users of the computer to delete
## 'temp' files created in this user subdirectory of /tmp.
fi
## MAKE NAME OF (TEMPORARY,SCRATCH) LIST FILE.
## Include a time-stamp to avoid overlaying other temp.lis files being
## currently viewed (and perhaps printed).
SECS=`date +%S`
MINS=`date +%M`
TEMP_FILENAME="temp.lis${MINS}${SECS}"
## MAKE THE FULLNAME OF THE LIST FILE --- in $TEMPDIR/$USER
## if it exists, otherwise in $HOME/tmp. The filename is in var OUTLIST.
##
## By calling this script with 'dot' --- . $FEDIR/scripts/set_localoutlist
## --- the OUTLIST var is available to the calling script.
if test -w $TEMPDIR/$USER
then
OUTDIR="$TEMPDIR/$USER"
OUTLIST="$OUTDIR/$TEMP_FILENAME"
else
echo "
Could not find or create directory $TEMPDIR/$USER
with write permission for $USER.
Using $HOME/tmp for temporary list file.
"
OUTDIR="$HOME/tmp"
if test ! -d $OUTDIR
then
mkdir $OUTDIR
chmod 755 $OUTDIR/$USER
fi
OUTLIST="$OUTDIR/$TEMP_FILENAME"
fi
## FOR TESTING:
# set -x
## AT this point $OUTLIST is set. Now cleanup old & big 'temp.lis####' files
## ... but do it only about half or 1/4 the time, based on current SECS.
# if test \( $SECS -gt 10 -a $SECS -lt 41 \)
# if test $SECS -lt 15
if test $SECS -lt 30
then
##########################################################################
## Do cleanup in two sections:
## 1) Use 'ls -t' to remove all but N such files , where N=50, say.
## 2) Use 'ls -l' to remove temp files that are 'big', say bigger than 50 Meg.
##########################################################################
##########################################################################
## REMOVE ALL BUT 'N-most-recent' 'temp.lis####' FILES:
##
## to help avoid accumulation of up to 60x60=3600 temp.list files
## in $TEMPDIR/$USER or $HOME/tmp.
##########################################################################
## We used to use the 'find' command, but it is difficult to keep
## the find command from traveling through subdirectories of
## $TEMPDIR/$USER or $HOME/tmp --- in a way that avoids using 'cd'.
##########################################################################
## We will use 'ls -t' and 'grep' and 'tail' once ---
## and we use 'rm' in a 'for' loop, even though we would
## like to avoid the 'for' loop. Since there will generally be less
## than 100 files, the loop should always go quickly.
##########################################################################
## We can test with 'ls -l' in place of 'rm -f'.
##########################################################################
NFILES_KEEP=100
# NFILES_KEEP=50
NFILES_TAIL=`expr $NFILES_KEEP + 1`
## The +N feature of 'tail' is not working, on Ubuntu Karmic (9.10).
## Getting the following error msg:
## tail: cannot open `+101' for reading: No such file or directory
# TEMPLISFILES=`ls -t $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*' | tail +$NFILES_TAIL -`
## An alternative technique:
TOTFILES=`ls -t $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*' | wc -l`
if test $TOTFILES -gt $NFILES_KEEP
then
FILES2RM=`expr $TOTFILES - $NFILES_KEEP`
TEMPLISFILES=`ls -t $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*' | tail -$FILE2RM`
for RELFILE in $TEMPLISFILES
do
## FOR TESTING:
# ls -l $OUTDIR/$RELFILE
# echo "NOT a MOST-RECENT-${NFILES_KEEP} FILE."
rm -f $OUTDIR/$RELFILE
done
fi
##########################################################################
## REMOVE BIG 'temp.lis####' FILES:
##########################################################################
## As above, we avoid using a 'find' command with the '-size' parm.
##########################################################################
## We will use 'ls' and 'grep' once and then we use
## both 'ls -l' and 'rm' in a 'for' loop, even though we would
## like to avoid the 'for' loop. Since there will generally be less
## than 100 files, the loop should always go quickly.
##########################################################################
## We can test with 'ls -l' in place of 'rm -f'.
##########################################################################
######################
## Define Big = 10 Meg
######################
# BIGinBYTES=10000000
######################
## Define Big = 50 Meg
######################
BIGinBYTES=50000000
## FOR TESTING: (10 KB)
# BIGinBYTES=10000
TEMPLISFILES=`ls $OUTDIR | grep '^temp\.lis[0-6][0-9][0-6][0-9]*'`
for RELFILE in $TEMPLISFILES
do
## FOR TESTING:
# set -x
RELFILE_BYTES=`ls -l $OUTDIR/$RELFILE | awk '{print $5}'`
if test $RELFILE_BYTES -gt $BIGinBYTES
then
## FOR TESTING:
# ls -l $OUTDIR/$RELFILE
# echo "BIG FILE."
rm -f $OUTDIR/$RELFILE
fi
## FOR TESTING:
# set -
done
fi
## END OF if test $SECS -lt 30

Code for the 'xpg' script:

#!/bin/sh
##
## SCRIPT: xpg
##
## PURPOSE: This shell script starts up a GUI utility, nicknamed 'xpg', to
## browse AND/OR search AND/OR extract-match-lines AND/OR
## print a text file.
##
## FURTHER DESCRIPTION:
## This script calls Tcl-Tk script 'shofil.tk' to present the GUI
## and to do browse actions on the text file --- per user actions on
## the widgets of the GUI.
##
## By default, this script starts 'shofil.tk' in the BACKGROUND.
## (Uses an '&' with 'shofil.tk' for BACKGROUND execution.)
## But the user can supply a '-f' parameter to this script, to
## force FOREGROUND execution of 'shofil.tk'.
##
## 'shofil.tk' is basically an X-GUI-version of the Unix (SGI-IRIX)
## 'pg' command. The 'pg' command offered a way to 'page' back and forth
## through a text file in a Unix virtual terminal. The 'pg' command
## was similar to the 'more' and 'less' command --- a big step up from
## using the 'cat' command to browse a text file. 'cat' did not have an
## option to page back (or forward) in the text.
##
## 'shofil.tk' adds a unique extract-matching-lines facility as well as
## built-in print function --- in addition to browse & search functions
## similar to those of the non-GUI 'pg' command (which is like 'less')
## --- but the 'xpg/'shofil.tk' browse & search options are much nicer
## (easier to use, faster, feature-rich).
## CALLED BY: Can be called manually at a Unix shell prompt.
## Example: xpg <text-filename>
##
## Also could be called from within shell or Tcl-Tk scripts.
##
## One of many ways the 'xpg' script could be implemented on Linux/Unix/BSD:
##
## Each user could implement 'xpg' via a shortname alias
## put in their $HOME/.bashrc or $HOME/.bash_aliases
## or $HOME/.kshrc or $HOME/.profile (or whatever) file, like
##
## alias xpg='$DIRxpg/scripts/xpg'
##
## where $DIRxpg represents the install directory of the 'xpg'
## system files:
## Tk scripts:
## shofil.tk
## sho_colorvals_via_sliders3rgb.tk
## select_font.tk
## Shell scripts:
## xpg
## findANDshow_NOTstringsINfile.sh
## findANDshow_stringsINfile_plusminusNlines.sh
## set_localoutlist.sh
## Help file:
## shofil.hlp (plain text)
##
## The directory DIRxpg could be a subdirectory of the user's
## home directory --- example:
## $HOME/apps/xpg
##
## Alternatively, a link to that 'xpg' script could be used.
## Example: $HOME/apps/bin/xpg
## This 'xpg' script was adapted from the 'xpg' script of the 'FE xpg'
## subsystem, which is provided, along with other FE subsystems, at
## www.freedomenv.com. FE = Freedom Environment
## FE system Copyright 2006+ by Blaise Montandon
## MAINTENANCE HISTORY of this 'xpg' script for posting at wiki.tcl.tk:
##
## Written by: Blaise Montandon 2013aug05 Started based on the 'xpg' script
## of the 'FE xpg' subsystem which is
## packaged at www.freedomenv.com.
## Updated by: Blaise Montandon 20.......
## Set the installation directory of the set of 'xpg' files.
# DIRxpg="$HOME/apps/xpg"
DIRxpg="."
## Set USAGE_TEXT var.
SCRIPTBASENAME=`basename $0`
USAGE_TEXT="Usage: $SCRIPTBASENAME [-h] [-f] file1 [ file2 ... file8 ]
-h - gives this help
-f - to execute this utility in the foreground, rather than background."
## If this script was not executed on the command line and
## if no vars (filenames) were passed, pop a msg (using
## the 'zenity' utility) and exit.
## FOR TESTING:
# env > $HOME/env.lis
if test "$_" = "" -a "$1" = ""
then
zenity --info --title "xpg: Need filename." \
--text "\
No filename was passed to the 'xpg' script.
$USAGE_TEXT
Exiting."
exit
fi
## Otherwise, if this script IS executed on the command line
## and if no vars (esp. filenames) were passed, echo a msg and exit.
# if test $# -eq 0
if test "$1" = ""
then
echo "
$USAGE_TEXT
"
exit
fi
## Process 'xpg' command options with 'getopts'.
## '-q' (for QUIET) is not used for now. Maybe someday.
QUIET=false
FOREGROUND=false
while getopts qhf OPTION
do
## FOR TESTING
# echo "OPTION: $OPTION"
case $OPTION in
q)
QUIET=true
;;
f)
FOREGROUND=true
;;
h|?)
echo "
$USAGE_TEXT
"
exit
;;
esac
done
shift `expr $OPTIND - 1`
## STARTUP THE 'xpg' GUI --- using a loop.
## A max of 8 files is accepted --- to prevent
## starting up a huge bunch of instances. Who hasn't done that?
## NOTE: We are using a 'for' loop --- WITHOUT 'in' --- to handle getting
## filenames --- even filenames with embedded spaces. Ref: man bash
## FOR TESTING:
# set -x
## These vars can be changed to set the initial location
## and size of the shofil.tk window(s).
## USERS MIGHT WANT TO RESET THESE TO SUIT THEIR OWN
## PREFERENCES.
# export WINLOCX="80"
# export WINLOCY="80"
# export WINSIZEX="750"
# export WINSIZEY="450"
## CNT is used in the loop below to delay starting subsquent
## instances of shofil.tk.
## CNT is also used to limit input filenames to 8.
CNT=0
## The LOOP to display the files, with shofil.tk, for the
## filenames passed to this 'xpg' script. (max of 8 files used)
for FILE
do
## FOR TESTING:
# echo "
# FILE = $FILE"
if test $CNT = 0
then
CNT=`expr $CNT + 1`
else
## Pause before starting subsequent instances,
## to make sure they are noticed.
sleep 1
CNT=`expr $CNT + 1`
fi
## FOR TESTING:
# set -x
if test "$FOREGROUND" = "false"
then
$DIRxpg/shofil.tk "$FILE" &
else
$DIRxpg/shofil.tk "$FILE"
fi
## FOR TESTING:
# set -
if test $CNT -ge 8
then
exit
fi
done

POTENTIAL ENHANCEMENTS:

As I mentioned above, I have been using the 'xpg' utility for several years now and I have not found a need to add new features.

So I have no plans to make any enhancements at this time.

However, in the process of making a 'stand-alone' 'xpg' system from the 'integrated' 'FE xpg' system (with its Tk 'include' files making for even more files in the installation), I must confess that when I got all the features working, I had run out of energy to go through the procs section of the 'shofil.tk' script and clean up the procs and improve their organization (their order).

Also I ran out of steam to go through the comments of the 2 'find' scripts and the 'set_localoutlist.sh' script.

So, someday, when I have more energy, I may return to this page and try to touch up the code --- mainly the comments and the order of the procs.

Meaning of the "stand-alone" terminology

In making this 'stand-alone' version of 'xpg' from the 'integrated' version in the 'FE xpg' subsystem, I merged the code from about five Tk 'include' files that I used to set various GUI parameters, such as colors, fonts, and widget geometry (padding and border-widths).

Those '.tki' Tk include files were used, via Tcl 'source' statements, to provide a consistent user interface for the 3 main Tk scripts in the 'FE xpg' subsystem --- the 'shofil.tk' script and the scripts for the color-selector and the font-selector.

Hence this set of 'xpg' files is 'stand-alone' in the sense that when the color-selector or font-selector GUI's pop up, they may not have the same colors and fonts and widget-geometry as the 'shofil.tk' GUI.

But you can easily make the 3 GUI's look alike by editing the 3 Tk scripts to change, for example, the 'tk_setPalette' statement to give all 3 GUI's a common color.

Here is a situation in which my 'canonical' form for all three scripts comes in handy. The color-setting, font-setting, and widget-geometry setting code is located in the same area in all three scripts --- near the top of the code (or, sometimes, in the 'Additional GUI Initialization' section at the bottom of the code).

THE HELP FILE:

There is one more file to this installation that is not on this page --- the help file. This 'xpg' utility has a lot of operational features that need some explaining. I provide the help file on a separate page --- Help file for the 'xpg' file browser utility. Methods of setting up the 'xpg' command for execution are described in that help file.

In that help and in the 'shofil.tk' code above, it is assumed that the following 8 files of the 'stand-alone' 'xpg' system are installed, together, in a single directory --- such as $HOME/apps/xpg --- not in several separate sub-directories, as they are in the 'integrated' 'FE xpg' system.

Pardon me for posting all this code here --- BUT I think we Tcler's need some examples of very useful code that can help us counter the claims of some nattering nabobs of negativism that 'Tcl-Tk is dead'.

Tcl-Tk not dead for me. I hope it is not dead for you. If you could use the 'ShowAllMatches' capability of this 'xpg' utility, then I think you will find that, indeed, Tcl-Tk is not dead for you.

As I have said on at least one other code-donation page on this wiki ...

A BIG THANK YOU to Ousterhout for starting Tcl-Tk, and a BIG THANK YOU to the Tcl-Tk developers and maintainers who have kept the simply MAH-velous 'wish' interpreter going.

However, for your use, which is obtaining a slightly random value using current time as a source, you can also replace the entire code block with the following to avoid all the issues with leading zeros:

set secs [ expr { [ clock seconds ] % 60 } ]

uniquename 2013aug06: Thanks for the note. I had a feeling that there was a more compact way to do that formatting, but I had so many other issues to address, I had to adopt a 'whatever works' philosophy for that issue.

I will make the changes in a copy of the code that I use everyday, and post changes at a future date. (By the way, sometimes I return to a posting of my code and have so many little changes that it is easier for me to simply overlay the entire code contribution. So if you or anyone makes changes to the code above, those changes may be overlaid at a later date. So it is best to explain the suggested changes as you have done here.)

uniquename 2013aug08: I thought a little more about the code using the current seconds to adjust the location of the 'shofil.tk' window each time it pops up. I am using numbers between 0 and 59 to set a number of pixels for the adjustment. However, whenever 0 is returned there is no adjustment --- and when the seconds are only 1 or 2 or 3, there is hardly any visible difference of a 'shofil.tk' window from a 'shofil.tk' window beneath it. So, when I get back to touching up that little bit of code, I will probably do something like

set secs [expr {$secs + 10}]

so that I am using numbers between 10 and 69 (pixels) --- rather than between 0 and 59 --- to place a new 'shofil.tk' window.

(Actually an adjustment of 20 pixels, rather than 10, may be better in this era in which we are seeing a transition to monitors with 'retina display' resolutions of about 2000-plus pixels by 1400-plus pixels.)

(And, Tk offers us Tcler's the option to use units other than pixels, like inches or millimeters.)

By the way, this code section brings up why I don't mind writing several lines of code rather than trying to write 'one-liners'.

Often, I like to put some comments explaining why I did various things that might be embedded rather cryptically in a one-liner. Implementing the code in several lines allows me to easily comment the code --- and it is much more readable/understandable to me ('at a glance') if/when I look at the code months later --- and it will probably be much more readable and understandable for other Tcler's.

If the code executes in only a few micro-seconds (and if it is not in a loop that is executed many 100's of times), then there is no significant harm in taking this approach.

I think Ousterhout had some comments to this effect in a coding-philosophy document of his from back in the 1990's. If I can find the quote(s), I will post them here.

RLE (2013-08-08): I generally agree with your sentiment about "cryptically" embedded. However, I was not recommending one line because it was "one line" but because, in my opinion, the one line was significantly more understandable as to the true intent of the code. In your instance above, I'd see the following math expression:

set offset [ expr { [ clock seconds ] % 60 + 20 } ]

As far more understandable as to the intent. Generate a number ranging between 20 and 79 using the current "seconds" value as a source, rather than this:

In this instance, I find the "spread apart" version less understandable. In other situations, spreading apart the steps is indeed helpful in understanding, I just do not feel that this particular instance is a good example for "spreading apart".

uniquename 2013aug08: As the French say, too-shay (phonetically speaking). I think you are quite right in this case. I could put comments about 'why the 20?' above that single line --- and for 'newbie' Tcler's, comments on 'why the percent sign and the 60?'.