[mu TECH] 'counter' a scripting util...

From: Alfie Costa (agcosta@gis.net)
Date: Thu Sep 14 2000 - 19:07:42 CEST


Good Afternoon Dear International Scriptophiles,

This post describes 'counter', a script for reducing the size of, (and speeding
up), other 'ash' scripts that use simple loops. Seek it here:

 http://www.gis.net/~agcosta/mu_files/counter.gz

'Counter' is inspired by 'jot', a util which I read about in a thick
O'Reilly anthology called 'Unix Power Tools'. What 'counter' does is
display a series of numbers or strings, sort of like a 'FOR...NEXT'
loop in BASIC, or like a 'for(,,)' loop in C. Some examples
of this script at work...

 #counter 5
 1 2 3 4 5

This might be good when a script uses a loop, and the loop has to
go a certain number of times. The usual way is something like:

 C=1
 while [ $C -lt 5 ]
 do
  echo $C
  echo Do some other stuff...
  C=`expr $C + 1`
 done

Yet that is slow, because 'expr' is called five times.
The 'counter' version of this code would be...

 for f in `counter 5`
 do
  echo $f
  echo Do some other stuff...
 done

More examples:

When there's two parameters, the first is the start, the second the
finish.

 #counter 5 10
 5 6 7 8 9 10

When the first parameter is bigger than the second, 'counter' counts
backwards.

 #counter 10 5
 10 9 8 7 6 5

It also will count negative numbers.

 #counter 2 -2
 2 1 0 -1 -2

If there's a third parameter, then that's the increment, (or 'STEP' in
BASIC); this number can't be negative...

 #counter 1 10 2
 1 3 5 6 7 9

...and doesn't need to be negative.

 #counter 10 1 2
 10 8 6 4 2

The fourth parameter is a prefix string. If you use it, you have to include
the third parameter too, even if it's '1'. Maybe that is lazy coding, but I
hope it is a way of making 'counter's syntax simpler; it's hard to decide.
It wouldn't be hard to add a lot of switches.

 #counter 1 3 1 file
 file1 file2 file3

The fifth parameter is a suffix string.

 #counter 1 3 1 file .txt
 file1.txt file2.txt file3.txt

The sixth parameter is a format string. The syntax is the same as the
'awk' language 'printf', which is usually the same as the C language
'printf'.

 #counter 1 3 1 file .txt "%.2i"
 file01.txt file02.txt file03.txt

The default format string is "%s"; if the sixth parameter is "", (an empty
string), the script checks for that and uses the default instead, which is,
again, "%s".

A bonus feature and/or complication: in the 'awk' code that does all the
work, there are two extra dummy parameters. Here's a code fragment.

 printf("%s'"$convert"'", a, i, i ,i )

The last two 'i's are the dummies, and aren't strictly needed. The extra
dummy parameters allow the format string to have up to three numbers formatted,
not just one.

 #counter 9 12 1 "" "" "%i=%X"
 9=9 10=A 11=B 12=C

Which makes a little table of decimal/hexadecimal numbers. This
business with the two dummy parameters was programmed by accident,
sort of, it did no harm, and it seemed useful to keep it.

A way to print the same thing five times...

 #counter 1 5 1 "" "" Mu
 Mu Mu Mu Mu Mu

The seventh and last parameter is a character separator. It can be any
string.

 #counter 1 5 1 "" "" "" "!"
 1!2!3!4!5

 #counter 1 5 1 "" "" "" ""
 12345

Try this next line on a color terminal...

 #counter 31 40 1 "mu" "Linux" "\033[%.2im"

A crawling horror:

It's easy to use spaces as separators, but not to get a 'for...do' loop
to see them. For example:

 #counter 1 2 1 "C:\\\Windows\\\file number" ".txt"

C:\Windows\file number1.txt C:\Windows\file number2.txt

It looks OK, but try it in a loop...

 for f in `counter 1 2 1 "C:\\\Windows\\\file number" ".txt"`
 do
        echo $f
 done

C:\Windows
          ile
number1.txt
C:\Windows
          ile
number2.txt
C:\Windows
          ile
number3.txt

...The 'f' turned into a control character. Add a few more "\\"s
to protect the 'f'...

 for f in `counter 1 2 1 "C:\\\\\Windows\\\\\file number" ".txt"`
 do
        echo $f
 done

C:\Windows\file
number1.txt
C:\Windows\file
number2.txt
C:\Windows\file
number3.txt

...better, but the space being interpreted as a separator. Here's a simpler
case of the same trouble:

 for f in a "b c" d e
 do
        echo $f
 done

a
b c
d
e

...the space is honored, it is good. But try it in backquotes...

 for f in `echo a "b c" d e`
 do
        echo $f
 done

a
b
c
d
e

...it fails. Try it with a '\' to protect the quotation marks...

 for f in `echo a \"b c\" d e`
 do
        echo $f
 done

a
"b
c"
d
e

...Sadly, it seems to be nearly impossible to do. The answer is
in the way the shell processes the command line. According to
"Command line psychology 101", a nice SunWorld article, which
incidentally can be found here:

http://www.sunworld.com/swol-02-1998/swol-02-unix101.html

...the command line is parsed in this order:

    1. History substitution (except for the Bourne shell)
    2. Splitting words, including special characters
    3. Updating the history list (except for the Bourne shell)
    4. Interpreting single and double quotes
    5. Alias substitution (except for the Bourne shell)
    6. Redirection of input and output (< > and |)
    7. Variable substitution (variables starting with $)
    8. Command substitution (commands inside back quotes)
    9. File name expansion (file name wild cards)

...and so the problem is that step 8, (command substitution), takes place
after step 4, (interpreting quotes).

There is a kludge though...

 eval set -- 'a "b c" d e'
 for f in "$@"
 do
        echo $f
 done

a
b c
d
e

...therefore, however unbeautiful, this works...

 eval set -- `counter 1 2 1 \\\\\""C:\\\\\Windows\\\\\file number"".txt"\\\\\"`
 for f in "$@"
 do
        echo $f
 done

C:\Windows\file number1.txt
C:\Windows\file number2.txt

Well, it would be nice to find a simpler way of using spaces in a loop.

Notes:

Maybe 'counter' should eventually be put on the mu boot disk -- "eventually",
because at present there's probably no room for it. With luck, some of the big
mu scripts can be shortened enough using 'counter', and the savings in space
will allow it to fit. Maybe I should have tried to shorten some of those
scripts before posting this; intuition is bold.

The code itself is an 'ash' wrapper for a long 'awk' command line. 'ash'
parses the command line, looks for various kinds of bad data, and then
passes all the good data on to a little 'awk' script.

The 'awk' code is simple, though ugly, due to many quotes, and two extra
'printf's that are needed to get the field separators just right.

The 'ash' wrapper code provides several error messages. The final error
message, (and line of code) may need explaining:

 [ $? -ne 0 ] && \
 Error "in $0's inner awk script. A bad conversion string?"

...this comes right after the 'awk' code, the error messages of which are
directed to '/dev/null'. The idea is to prevent buggy scripts that call
'counter' from reporting confusing error messages that begin with "awk:".

Naming the baby: maybe 'counter' is not the best name for this script; or
there might not be some other *nix util named 'counter'.

Last, a crude benchmark. Tested on a 200mhz CPU, muLinux installed on
a umsdos file system. Two one liners, timed by counting out loud, and the
results were...

About 2 minutes...
  # x=1;while [ $x -lt 1000 ];do;echo -n "$x ";x=`expr $x + 1`;done

Versus 2 seconds...
  # for x in `counter 1000`;do;echo -n "$x ";done

(Note that the last line could be simplified to just 'counter 1000', but
the idea was to allow for the 'for...do' loop overhead.)

G'day!

---------------------------------------------------------------------
To unsubscribe, e-mail: mulinux-unsubscribe@sunsite.auc.dk
For additional commands, e-mail: mulinux-help@sunsite.auc.dk



This archive was generated by hypermail 2.1.6 : Sat Feb 08 2003 - 15:27:15 CET