Asku, a Swiss Army Knife for Scripts

Version beta 3

Contents
Warnings
Introduction and Examples
   Get user input
   Simulating the unix "true" utility
   Simulating the unix /dev/null
   Simulating the unix "echo -n" utility
   Simulating the unix "sleep" utility
   Simulating the unix "date" utility
   Simulating the Unix "head", "tail", and "tac" utilities
   Simulating the Unix "grep" utility
   Simulating the Unix "tee" utility
   A better "more" utility
   Simulating the Unix "tr" and "sed" utilities
   Simulating the Unix "stat" utility
   Simulating the Unix "which" utility
   Combining asku functions
   Dealing with nontrivial DOS paths
   Dealing with nontrivial Unix paths
General Usage
INPUT
OUTPUT
TEMPLATE and its Commands
   Intro
   TEMPLATE commands
   Debugging TEMPLATE
TYPE
DEFAULT
HELP
MAX, P8, SEP
Download
History

Warnings

Introduction and Examples

Asku is a general purpose utility for computer scripts. It is like a Swiss Army knife: it can do anything. Well, a lot in any case.

First, the screwdriver, nail file, bottle opener, and corkscrew parts. Asku can ask the script user for a:

  1. list item using a menu,
  2. color,
  3. number,
  4. existing folder, or
  5. string.
Then asku will format the user input in exactly the form you need. The above are all very specific tasks.

Next the "knife and scissors" generic parts of asku. It can also process files, or standard input, in a variety of quite complex ways. This is especially useful for MS Windows DOS scripts, where build-in tools are limited. Unix scripts have far better tools. But some people write scripts for both DOS and Unix, and asku is a way to do that in a unified way. Further, in Unix asku may still be able to do things that these tools will not do, or will not do easily, or that require multiple image activations. (Asku is statically linked and does not require additional libraries to be loaded either.) Also you do not have to remember thousands of options, and asku can be more understandable. The reason is that asku is to a considerable amount "programmable."

Examples of all these are given in the next subsections.

Get user input

The first example asks the user to select one of the gif files in the current folder by pressing its key, then run some script mktrans.bat with as argument the selected file, *?, between double quotes, *D:

   dir /b/o *.gif > list.lst
   asku.exe menu "?list.bat" "call mktrans.bat *D*?*D*;" "gif file"
   call list.bat
(This assumes Microsoft DOS. A Unix C-shell script would use "ls -1" instead of "dir /b/o" and "source" instead of "call".) Asku uses the list of gif files in list.lst to create a menu for the user, then creates list.bat that will run mktrans.bat on the selected file. (If the user quits, instead of selecting a file, no file list.bat will be created. You may want to account for that possibility explicitly in actual application, rather than just let things crash.)

Especially in DOS, it might be more convenient to provide the "root" file name, without the .gif file type, and the .gif file type as two separate arguments to mktrans. That can be done as

   dir /b/o *.gif > list.lst
   asku.exe menu "?list.bat" "call mktrans.bat *D*r0/*D *x0/*;" "gif file"
   call list.bat
Inside mktrans.bat,
   asku.exe color "?color.bat" "*:*F/set col=*@/*;" "color" "FFFFFF" "?help.txt"
   call color.bat
could ask the user for the color to make transparent, with default white (FFFFFF). Asku would then create a batch file color.bat that would set a variable col to the RGB values, as decimal fractions separated by slashes. File help.txt could contain an explanation of transparancy; basic help on selecting colors is already built into asku. If help.html is a web page instead of a text file, it can be loaded into the browser on user request by replacing "?help.txt" with "*start help.html" (DOS) or "*xdg-open help.html" (Linux).

The next example asks the user to select from a list of folders, where each folder contains a file marker.txt:

   dir /b/o/s marker.txt > list2.lst
   asku.exe menu "?list2.bat" "call proc.bat *?*;" "folder" "ISO-8859-1" - 0 2 "*Y"
   call list.bat
Unix would use "ls -1 */marker.txt" or "find . -name marker.txt" instead of "dir /b/o/s". The final two arguments of asku strip away the marker.txt and current working folder from the folder names. The *Y is a generic folder separator, \ for DOS and / for Unix. The selected folder (default is the ISO-8859-1 one) is then used by a script "proc.bat" to do whatever. Like maybe convert a file from a selected encoding to UTF-8. Perhaps unwisely, there is no additional help provided to the user in selecting.

Simulating the unix "true" utility

Set the error status to 0:

   asku.exe - -
This is a useful in MS Windows DOS, as DOS offers no guarantee that the error status errorlevel is reset by anything else than the find and choice commands. To set the error status to, say, 2, use:
   asku.exe - - "*q2/"
Note that though Microsoft does not say so, in real life cmd.exe resets the error status to 0 in a goto statement, like tcsh scripts do. That is somewhat of a pain, as you can only set the return status at the very end of a script.

Simulating the unix /dev/null

If you want to have the output of a command just go away, in unix you direct it to /dev/null. In DOS, you can direct it to device NUL. However, that opens a file handle for NUL in the current folder, one that is never closed. It is ugly, but not a great problem for DOS in a modern Windows window, because then there are zillions of file handles. But if it bothers you, one possibility is to use asku, like say,

   chcp | asku.exe - -

Simulating the unix "echo -n" utility

Print a string without starting a new line at the end in DOS:

   asku.exe - - "The current folder is: *" & cd
The final * in the quoted template is for preserving the trailing space and is further ignored. In unix tcsh, you would use "; pwd" instead of "& cd". Note that a simpler way to do the above would be
   asku.exe - - "The current folder is: *w*;"
Here the *w prints the working folder and the *; at the end provides a Newline.

Simulating the unix "sleep" utility

To pause for 2 seconds on Windows XP, Vista, and 7:

   asku.exe - - "*e23"
This is like a unix "sleep 2" command. Note: On unix specify the wait as "*e20", unless you are very patient. (Windows uses milliseconds where Unix uses whole seconds.)

Simulating the unix "date" utility

The following command prints the current date and time like, say, 01/20/14 09:54:12:

   asku.exe - - "*m/*d/*y *h:*i:*s*;"
The next one prints it as, say, Monday, Jan 20, 2014, 9:54:12 am EDT (-0500):
   asku.exe - - "*eD0, *eM3 *d, *ey4, *eH1:*i:*s *eN0 E*eS0T (*eZ0)*;"
The *eH1 outputs the hour for a 12-hour clock, leaving any leading zero away.

You might want to put a point after the month abbreviation Jan for proper style. But simply replacing *eM3 by *eM3. will not work correctly because May should not have a point. As discussed later, *(0...*) edit sequences are needed. In this case, you would need to do it as:

   asku.exe - - "*eD0, *z*eM3.*(0*/May./May*)*o/ *d, *ey4, *eH1:*i:*s *eN0*;"
More sophisticated, the next example would truncate months 5 characters or longer to three characters plus point, but leave months 4 characters or shorter unchanged:
   asku.exe - - "*eD0, *z*eM0*(0*/*([-][-][-]*)[-][-][-]?/*(1*).*)*o/ *d, *ey4, *eH1:*i:*s *eN0*;"

Somewhat more fanciful, the next line produces a "clock", updating the displayed time every second (use Ctrl+c to terminate):

   asku.exe - - "*l/[*h:*i:*s]*e13*e?0*13*b"
Unix users use *e10 rather than *e13. The *l/ and *b commands provide an infinite loop, as reading backs up from *b back to *l/. This is discussed further in later examples. The *e?0 refreshes the date and time data and the *13, Carriage-Return, sends the screen cursor back to the start of the line.

A clock that uses a 12-hour format, replacing any leading zero in the hour by a blank, and reformatting am and pm as A.M. and P.M. is:

   asku.exe - - "*=aA.M. pP.M./*l/[*eH2:*i:*s *eN1]*e13*e?0*13*b"
Unix users use *e10 rather than *e13. The 1 in *eN1 leaves out the "m" from "am" and "pm". The *=.../ command replaces characters, here "a" and "p", by strings. In this way, you could write am and pm in Greek, if the terminal supports the Greek character encoding you use. It gets messier if you want to put weekday names and month names in a language different from English, but it can be done using *(0...*) edit sequences, much like the May./May example above, as discussed in the edit sequence discussion.

Simulating the Unix "head", "tail", and "tac" utilities

Type the lines of an input file FILE, starting from the third line, and terminating at the fifth line from the end:

   asku.exe "?FILE" - "*j2/*l2:5?/*c*b"
   asku.exe "?FILE" - " *j2/ *l2:5?/ *c *b"
(The second line above has added spaces before the * commands to improve readability. These spaces should not be in the actual command.) The *l2:5?/ "loop" and *b "back" commands produce a loop over the lines of input file FILE. In particular, reading at *b backs up to *l as long as the current input file line stays within bounds. The 2 in *l2:5?/ sets the lower input line bound to line 2 from the start of FILE, and the :5? sets the upper bound to 5 lines from the end. (The 2 could be omitted.) The *j2/ "jump" command sets the initial input file line to line 2 from the start. The *c "copy" command copies the next input file line to the output and advances the line position by one. So while looping over TEMPLATE, the successive lines of FILE are send to output. (Output is specified here as -, standard output, which normally means the computer screen.)

The input could be piped in. To pipe in a file FILE, in DOS use

   type FILE | asku.exe - - "*j2/*l2:5?/*c*b"
   type FILE | asku.exe - - " *j2/ *l2:5?/ *c *b"
In unix, use cat instead of type (and use FILEu instead of FILE if you are trying this out in the asku source distribution). Or use
   asku.exe "?FILEu" - "*l/*c*b" | asku.exe - - "*j2/*l2:5?/*c*b"

Warning: if FILE does not end in a Newline, Unix pipes add one while DOS ones do not. But proper text files should enter in a Newline.

To type the lines of a file in inverse order, starting from the fifth line from the end, and ending at the third line from the start:

   asku.exe "?FILE" - "*j5?/*l2/*j-1/*c*j-1/*b"
   asku.exe "?FILE" - " *j5?/ *l2/ *j-1/ *c *j-1/ *b"
This is like you can get with unix "head", "tail", and "tac" commands, though not POSIX compliant. The *j5?/ sets the initial input line to line 5 from the end of FILE. The *c copies the next line to the output and advances the line position by one. To compensate for the advance, the following *j-1/ command backs up a line again. The *j-1/ preceding the *c command is the one that causes the lines to be processed in inverse order as it reduces the line position by 1 each time through the loop.

Note that if you pipe in FILE, instead of giving it as a parameter like above, the file size will be limited by the internal buffer that asku uses to save input lines. Changing the buffer size means recompiling asku, which requires that g77 or gfortran is installed.

Actually, it is not really the file size that is limited by the asku buffer. It is really the amount that asku can back up that is limited. The next example requires only that the asku buffer can hold 5 lines, regardless how big FILE is. It writes the first 5 lines of FILE in inverse order, then the second 5 in inverse order, etcetera:

   type FILE | asku.exe - - "*l/*j+5/*l-5:+0/*j-1/*c*j-1/*b*b"
   type FILE | asku.exe - - " *l/ *j+5/ *l-5:+0/ *j-1/ *c *j-1/ *b *b"
OK, I do not know either why you would want to do that. But it does demonstrate that asku can execute nested *l.../...*b loops, and do things that unix utilities will not easily do. (Nested loops will be demonstrated later for, for example, paging the text of an html file like the one you are reading.) Note also that you could do other things with each line in addition, if you wanted. Like only type it if it contains the search string, or only if it does not contain the search string, using the *n command. And/or you could replace single 8-bit character characters and/or complex strings by other characters or strings using gathering and *(0...*) edit commands. That is much like the unix tr, respectively sed commands.

Simulating the Unix "grep" utility

Output the lines of a file containing a capitalized word followed by a colon:

   asku.exe "?FILE" - "*/[A:Z][a:z]?:/*l/*n*(*c*)*(*)*b"
   asku.exe "?FILE" - " */[A:Z][a:z]?:/ *l/ *n *( *c *) *( *) *b"
This is like a unix "grep '[A-Z][a-z]*:' FILE". The */.../ command defines the string to search for to be a capital, [A:Z], followed by any amount of lowercase letters, [a:z]?, followed by a colon. The *n*(FOUND*)*(NOTFOUND*) executes FOUND or NOTFOUND depending on whether the next input file line contains the search string, and *c copies the next line to the output.

To return an error status if no strings are found at all, you can use, for example,

   asku.exe "?FILE" - "*/[A:Z][a:z]?:/*l/*n*(*k/y*.*c*)*(*)*b*vn/*(*q1/*)*(*)"
The *vn/*(YES*)*(NO*) tests whether the default storage location has a nil or undefined value, while the *k/y*. sets a nonnil value 'y' in it.

To output the lines of a file not containing a capitalized word followed by a colon:

   asku.exe "?FILE" - "*/[A:Z][a:z]?:/*l/*n*(*j+1/*)*(*j-1/*c*)*b"
   asku.exe "?FILE" - " */[A:Z][a:z]?:/ *l/ *n *( *j+1/ *) *( *j-1/ *c *) *b"
This is like a unix "grep -v '[A-Z][a-z]*:'". To output the lines of a file containing a capitalized word followed by a colon, and also a $ sign (encoded as *I),
   asku.exe "?FILE" - "*l/*/[A:Z][a:z]?:/*n*(*/*I/*n*(*c*)*(*)*)*(*)*b"
   asku.exe "?FILE" - " *l/ */[A:Z][a:z]?:/ *n *( */*I/ *n *( *c *) *( *) *) *( *) *b"
which uses nested *n commands. In unix you could pipe a first grep command into a second. If the $ sign must be behind the colon, separated from it by spaces, use more simply:
   asku.exe "?FILE" - "*/[A:Z][a:z]?: ?*I/*l/*n*(*c*)*(*)*b"
   asku.exe "?FILE" - " */[A:Z][a:z]?: ?*I/ *l/ *n *( *c *) *( *) *b"
Or if it can be anywhere behind the colon, use:
   asku.exe "?FILE" - "*/[A:Z][a:z]?:[-*I]?*I/*l/*n*(*c*)*(*)*b"
   asku.exe "?FILE" - " */[A:Z][a:z]?:[-*I]?*I/ *l/ *n *( *c *) *( *) *b"
To output the lines of a file containing a capitalized word followed by a colon in lines 10 to 5 from the end of the file in inverse order, use:
   asku.exe "?FILE" - "*/[A:Z][a:z]?:/*j5?/*l11?:5?/*j-1/*n*(*c*)*(*)*j-1/*b"
   asku.exe "?FILE" - " */[A:Z][a:z]?:/ *j5?/ *l11?:5?/ *j-1/ *n *( *c *) *( *) *j-1/ *b"
In unix, you could pipe a tail into a head into a grep into a non-POSIX command like tac or head -r.

Another way to achieve these effects is to use edit sequences. Edit sequences allow you to kill off lines that contain or do not contain a search string. This is more elaborate, and typically requires the use of markers, but it allows for far more complex, and multi-line searches.

Simulating the Unix "tee" utility

Show error messages to the user on the screen, but also keep a log file LOGFILE of what is shown on the screen, if anything:

   COMMAND | asku.exe - "+LOGFILE" "*/INDICATOR/*l/*n*(*c*)*(*)*b"
   COMMAND | asku.exe - "+LOGFILE" " */INDICATOR/ *l/ *n *( *c *) *( *) *b"
This is like the unix tee command. COMMAND is a command that outputs error messages, and INDICATOR is a search string that finds the error messages. If there are no error messages, no file LOGFILE is produced. In any case the error status is 0.

For example, if "COMMAND" is "latex --interaction=nonstopmode FILE", it will produce errors marked by an exclamation mark (encoded as *E) at the start of the line. The start of the line can be searched for as *10. So the next command will type each error message and the next three explanatory lines, separating errors with a blank line:

   latex --interaction=nonstopmode FILE | asku.exe - "+LOGFILE" ^
"*/*10*E/*l/*n*(*c*c*c*c*;*j-3/*)*(*)*b"
In Unix tcsh, use \ instead of ^ to break a command line into multiple screen lines.

There might however be problems that are not indicated by an exclamation mark, and you may want to see these in context with the exclamation mark ones. (Seeing them separately is of course trivial.) Assuming the other problems you are interested in are indicated by the strings "Warning:" and "erfull" and require no context lines, the appropriate third argument of asku is:

   latex --interaction=nonstopmode FILE | asku.exe - "+LOGFILE" ^
"*l/*/*10*E/*n*(*c*c*c*c*;*j-3/*)*(*)*j-1/*/Warning:/*n*(*c*)*(*)*j-1/*/erfull/*n*(*c*)*(*)*b"
I would not quite know how to do the variable context lines using Unix tools.

A better "more" utility

The following command allows the user to page a file:

   asku.exe "?FILE" - "*l/*pp/*c*b"
   asku.exe "?FILE" - " *l/ *pp/ *c *b"
This is like the DOS "more", or unix "more" or "less" commands. But it does not have the annoying habit of "more" to exit as soon as it sees end of file. This is a real problem in script files where you want to redraw the screen after "more" exits, and you do not know whether the file ended (must pause to show final output) or the user quit (should not pause). Not to mention that the user might have wanted to back up at the end of file. Also, asku tells an inexperienced user straight up front how to get help. The help includes a brief description of the asku "regular-expression-like" search string syntax. Finally asku leaves markers to see what is going on, (except on scrolling using the Return key), because other pagers always leave me wondering where I am in the file. Even their Space key leaves me groping for where the last page started.

Note however that unlike "more", the input to the *pp/ command cannot be piped in; it must be a file. That is a limitation to the libraries used by asku.

Irrelevant lines can simply be removed from the paged area. In the next example, the first two lines and the last 4 are kept hidden:

   asku.exe "?FILE" - "*j2/*l2:5?/*pp/*c*b"
   asku.exe "?FILE" - "*j2/ *l2:5?/ *pp/ *c *b"
Or if you only want to show the part between the \begin{document} and \end{document} lines of a LaTeX file, use:
   asku.exe "?FILE" - "*l/*/\end{document}/*n*(*l:+1/*/\begin{document}/*n*(*l+0/*pp/*c*b*j0?/*)*(*j-2/*)*b*j0?/*)*(*)*b"

If you want to make changes to the lines of the file before the user sees them, enclose the *c command in a *z...*o/ construct. For example,

   asku.exe "?FILE" - "*l/*pp/*z*#3/:*c*(0*=*0:*31 *9:*10+0 *13+0/*)*o/*b"
   asku.exe "?FILE" - " *l/ *pp/ *z *#3/: *c *(0 *=*0:*31 *9:*10+0 *13+0/ *) *o/ *b"
prefixes the lines by a 3-digit line number, *#3/, and suppresses control characters below Space except Tab, Carriage-Return, and Linefeed.

See also the *t.../ command about setting line truncation/wrapping, tab stops, and enabling UTF-8-type character counting.

Simulating the Unix "tr" and "sed" utilities

A previous "clock" example showed how you can do character replacements using the *=.../ command. It replaced "a" by "A.M." and "p" by "P.M.":

   asku.exe - - "*=aA.M. pP.M./*l/[*eH2:*i:*s *eN1]*e13*e?0*13*b"
   asku.exe - - " *=aA.M. pP.M./ *l/[ *eH2: *i: *s  *eN1] *e13 *e?0 *13 *b"
To replace strings instead of single characters, *(0...*) edit commands can be used. For example, to replace any "John" in FILE by "Joe", use
   asku.exe "?FILE" - "*l/*z*c*(0*/John/Joe*)*o/*b"
   asku.exe "?FILE" - " *l/ *z *c *(0 */John/Joe *) *o/ *b"
The *z accumulates the next output temporarily in a "gather buffer" instead of sending it to the output device. The *(0*/John/Joe*) edit command then replaces John by Joe in the gathered data. And finally the *o/ sends the gathered data to the output device. It does not delete the gathered data afterwards. However, the *z command discards any existing gathered data before gathering new.

In addition to simple strings, search strings may be replaced. The following example replaces any "John Doe" in FILE by J. Doe but leaves a lone "John" alone. More generally, it abbreviates any capitalized word if it is followed by whitespace and a capital:

   asku.exe "?FILE" - "*l/*z*c*(0*/*([A:Z]*)[a:z]?*(*W*W?[A:Z]*)/*(1*).*(2*)*)*o/*b"
In the search string, [A:Z] is the initial capital, [a:z]? the following lowercase letters, *W*W? one or more whitespace characters, and [A:Z] the second capital. (In a search string, *W stands for the normal whitespace characters Tab, Carriage-Return, Linefeed, and Space. To include all control characters as whitespace, use [*0:*32] instead of *W.) The *(...*) groupings in the search string allow the initial capital to be used in the replacement as *(1*) and the whitespace and final capital as *(2*). As you may guess from their format, *(1*) and *(2*) may be edit commands themselves, with their own character and string replacements. That allows more complex editing, in which the outer edit command provides a larger context for the inner edit commands.

The above example has the problem that if "John Doe" gets broken by a Newline, "John" will not be abbreviated. The following example fixes this by keeping 2 lines in the gather buffer:

   asku.exe "?FILE" - ^
"*z*c*l/*c*(0*/*([A:Z]*)[a:z]?*(*W*W?[A:Z]*)/*(1*).*(2*)*)*o1/*g*(0*/[-*10]?*10*([-]?*)/*(1*)*)*b*o/"
In this case a *(0*/[-*10]?*10*([-]?*)/*(1*)*) edit command was used to get rid of the first line in the buffer before reading the next one into it. The *g command resumes gathering without zeroing the gather buffer.

Another *(0*/*([A:Z]*)[a:...*) edit command could take care of multiple first names.

(Sorry, from now on you will need to cut and paste the parts of the template together. In the first version of asku, the template had a maximum length of 256 characters. You can see why I increased it to 32,768 characters.)

You might want to disable capitals that indicate the start of sentences in the above:

   asku.exe "?FILE" - ^
"*=*1 *2./*z*c*l/*c*(0*/.*W?[A:Z]/*(0*)*1*)
*(0*/*([A:Z]*)[a:z]?*(*W*W?[A:Z]*)/*(1*)*2*(2*)*)*o1/*g*(0*/[-*10]?*10*([-]?*)/*(1*)*)*b*o/"
This puts a Ctrl+a, *1, after such capitals, and uses Ctrl+b as abbreviation symbol instead of a point (to avoid introducing points that could be taken for end of line). During the *o1/ output, the initial *=*1 *2./ deletes the Ctrl+a and replaces Ctrl+b by a point.

Edit sequences can be nested inside others. Suppose you want to replace a bare \\ by \\[7pt] only if it is inside a \begin{array}...\end{array} environment. To do so, first put a marker between any \end and {array},

   *(0*/d*({array}*)/d*1*(1*)*)
Then use an edit sequence that finds the strings from \begin{array} to the next marker, and nest inside it an edit sequence that does the replacement,
   *(0*/n{array}[-*1]?/*(0*/\\*([-[]*)/\\[7pt]*(1*)*)*)
If you want to exclude the "n{array}" part from the replacements, enclose both that and the part to be changed between *( and *). Then use a *(2...*) edit sequence to do the replacements on the second enclosed part only;
   *(0*/*(n{array}*)*([-*1]?*)/*(1*)*(2*/\\*([-[]*)/\\[7pt]*(1*)*)*)
However, this is not needed here as the replacements do nothing to n{array}. The total command becomes
   asku.exe "?FILE" - "*=*1/*z*l/*c*b*(0*/d*({array}*)/d*1*(1*)*)
*(0*/n{array}[-*1]?/*(0*/\\*([-*O]*)/\\[7pt]*(1*)*)*)*o/"
The above example does assume that the entire file fits inside asku's gather buffer. For a really big file, you can keep say 9 lines at a time in the buffer, assuming that arrays extend over no more than 9 lines. Then the command becomes
   asku.exe "?FILE" - "*=*1/*z*l:9/*c*b*j9/*l/...OOO*g*c*b*o/"
where ... stands for the same three edit sequences as before,
   *(0*/d*({array}*)/d*1*(1*)*)*(0*/n{array}[-*1]?/*(0*/\\*([-[]*)/\\[7pt]*(1*)*)*)
and OOO stands for
   *o1/*(0*/[-*10]?*10*([-]?*)/*(1*)*)
which sends the first line of the gather buffer to the output device and then deletes it from the buffer. The final *o/ in the complete command dumps the final 8 lines of the file that are left in the gather buffer to the output device.

Simulating the Unix "stat" utility

Asku can produce various data on files, such as file type, size, and protection, and also take action on them. Note that many of the "stat" data are not really meaningful for Microsoft DOS.

But one that is is file size. To compare the file size of two files, use the asku *v command. For example, to test whether FILEu is smaller than FILE,

   asku.exe - - "*k/FILEu*k0/FILE*.*vSS*L/*(yes*;*)*(no*;*q1/*)"
Besides the message, this also returns an error status if FILE is bigger. To test whether the files are of equal size, replace *L by =. For bigger, use *G. For less equivalent or greater equivalent, swap FILEu and FILE. (Unix users can use < and > instead of *L and *G. DOS users need to escape those characters.)

Remember the example that typed the lines of a file in inverse order, starting from the fifth line from the end, and ending at the third line from the start? What if you want to make some changes to FILE before typing it in inverse order and saving it as NEWFILE? Using pipes that would look like

   type FILE | ...| asku.exe - "?NEWFILE" "*j5?/*l2/*j-1/*c*j-1/*b"
where ... stand for whatever commands make changes to the data in FILE. The above procedure has the problem that if FILE is greater than the asku input buffer, (524288 bytes at time of writing), asku crashes after outputting that amount. You could however use asku to test whether that is the case and take appropriate action:
   type FILE | ... | asku.exe - "?NEWFILE" "*k/FILE*k0/524288*.*vS*G/*(*l/*c*b*q2/*)*(*j5?/*l2/*j-1/*c*j-1/*b*)"
The *k/FILE*k0/524288*. commands put FILE in default storage location *@/ and 524288 in storage location *@0/. Then inside the *v.../ "verify" command, the S replaces FILE in storage location *@/ by its size in bytes. Then *G tests whether the number in *@/ is greater than the one in *@0/. If it is, in the following *(YES*)*(NO*) grouping YES (typing the file normally and returning error status 2) is executed. If it is not, NO (typing the file in inverse order) is executed. The script running asku can examine the returned error status, and if it is 2, it knows that the changed file has not been inverted. So it can fix it then. In DOS, the entire thing would look like:
   type FILE | ... | asku.exe - "?NEWFILE" "*k/FILE*k0/524288*.*vS*G/*(*l/*c*b*q2/*)*(*j5?/*l2/*j-1/*c*j-1/*b*)"
   if errorlevel 2 goto toobig
   if errorlevel 1 goto someprob
   goto good
   :toobig
   rename NEWFILE TMPFILE
   asku.exe "?TMPFILE" "?NEWFILE" "*j5?/*l2/*j-1/*c*j-1/*b"
   del TMPFILE
   :good
   echo Success!
All this avoids needless image activations and saving an intermediate file to disk for small files, while still handling big files correctly (which will be slower anyway.) You can use
   asku.exe - - "*vB/*@/*;"
to figure out the size of the input buffer in your build of asku.

If you have no disk FILE to check for size, or you have no clue what the changes do to the file size, use

   type FILE | ... | asku.exe - "?NEWFILE" "*k0/50000*l/*z*c*vB*L/*(*.*j0/*l/*c*b*q2/*)*(*)*b*.*j5?/*l2/*j-1/*c*j-1/*b"
This will try to read in the entire file into the input buffer first, line by line, without doing output. If in doing so it gets within 50 kB of the end of the buffer, it dumps the input unchanged to output and exits with status 2.

Another application of the *v.../ command is to test which one of two files is newer. For example,

   asku.exe - - "Asku.exe *k/asku.f*k0/asku.exe*.*vTT*G/*(must be rebuild*)*(is OK*).*;"
would tell you whether the asku source file asku.f is of later time than asku.exe (in which case asku.exe must be rebuild) or not.

The following command would test whether we have write access to file asku.f:

   asku.exe - - "*k/asku.f*.*vw/*(Yes*)*(Readonly*).*;"
The following example would convert ..\asku\FILE.tex (../asku/FILE.tex in Unix) to an absolute file specification, also removing the redundant CURRENTDIR\..\ from the result:
   asku.exe - - "*k/*w*Y..*Yasku*YFILE.tex*.*vF/*@/*;"
Note that since we did not have a test in the *v.../ command, we do not have a following *(YES*)*(NO*) construct. But we could in addition check for existence using a ?:
   asku.exe - - "*k/*w*Y..*Yasku*YFILE.tex*.*vF?/*(*@/ exists*)*(*@/ not found*).*;"
In unix, you could check whether you have read access to a file, whether you own it or not, as
   asku.exe - - "*k/asku.f*k0/1wx1wx1wx*.*vM:/*(yes*)*(no*)*;"
This checks the "stat" rwxrwxrwx file mode bits for all three types of read access. To check that you are in fact the owner, use
   asku.exe - - "*k/asku.f*.*vUI=/*(yes*)*(no*)*;"
You can also run another program from within asku using a *v!COMMAND/*(GOOD*)*(BAD*) construct. (Note that firewalls/virus checkers may not appreciate this.) Here COMMAND is the command to run the other program, which may contain *? and *@.../ commands. GOOD is executed if the program did not produce an error status, otherwise BAD is executed. For example,
   echo "file://%cd%\index.html#download" | asku.exe - - "*k/*f*.*v!start iexplore *@//*(good*)*(bad*)*;"
You will get "good" unless Internet Explorer does not exist. In Unix, you would use
   echo "file://`pwd`/index.html#download" | asku.exe - - "*k/*f*.*v*Exdg-open *@//*(good*)*(bad*)*;"

Simulating the Unix "which" utility

The next example searches for an executable file "latex.exe" in the PATH:

   asku.exe - - "*k0/latex*z*_PATH/;*(0*=;*;/*/\;/;*)*~ig/*l/*k/*f\*?.exe*.*v?/*(*@/*;*q/*)*(*)*b*q1/"
(If you do not have latex, use notepad.) The *_PATH/ command produces the value of the PATH variable. The *(0...*) edit command turns the PATH entries into separate lines, and also gets rid of any trailing backslashes. The *~ig/ command copies the gather buffer into a virtual input file. This is an important trick, because while asku can change the gather buffer, it can only loop over the input buffer. Using the *~ig/ command, asku can loop over the lines of the modified PATH.

To also search for latex.bat if there is no latex.exe, add a corresponding loop *l/*k/*f\*?.bat*.*v?/*(*@/*;*q/*)*(*)*b before the final *q1/.

If for some reason you want to adapt this to Unix, maybe because it is more customizable than "which", note that \ becomes /, which must be encoded as *F inside the edit command, and PATH entries are separated by : instead of ;. So:

   asku.exe - - "*k0/latex*z*_PATH/:*(0*=:*;/*/*F:/:*)*~ig/*l/*k/*f/*?*.*v?/*(*@/*;*q/*)*(*)*b*q1/"
Using *Y instead of \ or / will select the right separator automatically. Also note that Unix does not use extensions for executable files, a significant shortcoming.

Note also that Windows programs, like iexplore, although available through the DOS "start" command, do not usually show up in the PATH. You could consider something like

   dir /b/s "C:\Program Files\iexplore.exe" 2>1 | asku.exe - - "*/:/*n*(*c*)*(*q1/*)"
Pretty slow on my XP machine.

Combining asku functions

Because asku is so general, you can do a lot in a single asku call.

The first example would run backup job "backup.bat" at 01:00 am every day:

   asku.exe - - "*k0/0100*l/*e64*e?0*k/*h*i*.*ve/*(Backup on *m/*d/*y *v!call backup.bat/*(OK*)*(BAD*)*;*)*(*)*b"
Unix users use *e61 instead of *e64, leave out call, and encode ! as *E. To terminate if the backup job was successful, put *j-1/ behind *OK*;. Note that your virus scanner may not like it when asku accesses cmd.exe.

For the next example, recall the earlier example where the user was asked to select a .gif file and then an external batch file mktrans.bat is executed with the double-quoted file name and extension as parameters:

   dir /b/o *.gif > list.lst
   asku.exe menu "?list.bat" "call mktrans *D*r0/*D *x0/*;" "gif file"
   call list.bat
For various reasons, you might want to hard-code the file name and extension directly into a batch file, rather than giving them as arguments. For example, that removes the need to maintain a local tmp.bat as well as an external batch file mktrans.bat. A single local mktrans.bat will do the job. So, assume that mk0.bat is the desired local mktrans.bat file, except that it has P1 wherever the file name is required and P2 wherever the extension is. Then the desired local mktrans.bat can be produced as
   dir /b/o *.gif > mktrans.lst
   asku.exe "?mk0.bat" "?mktrans.bat" "*am/*k1/*r0/*k2/*x0/*l/*z*c*(0*/P1/*@1/*)*(0*/P2/*@2/*)*o/*b" "gif file"
   call mktrans.bat
The *am/ command asks for the gif file, activating the asku "menu" function. The combination with *(0...*) edit commands allows P1 and P2 in mk0.bat to be replaced by the selected actual file name and extension in mktrans.bat.

An alternate way of achieving the same effect is to redirect the input and output to mk0.bat and mktrans.bat with *~.../ commands after the user has chosen the file:

   dir /b/o *.gif > list.lst
   asku.exe menu "?list.bat" "*k/mk0.bat*~i?/*k/mktrans.bat*~o?/*k1/*r0/*k2/*x0/*l/*z*c*(0*/P1/*@1/*)*(0*/P2/*@2/*)*o/*b" "gif file"
   call mktrans.bat

As another example, I use asku in my scripts as a last resort to show html help files if I cannot find a web browser on the system. With some decent formatting of the html source, including table rows, asku does a reasonable job of showing just the text:

asku.exe "?index.html" - "*tu8/
*/*Lbody[*W*G]/*l/*n*(
*/*Lh[1:6]/*j+1/*l+0/*pp/
*z*1*c*2*(0*=*13/*)*(0*/*10/*;*)*(0*/*G/ *G*)
*(0*/*1*([-*L*G]?*G*)/*1*La *(1*)*)*(0*/*(*L[-*L*G*;]?*)*;/*(1*) *G*;*)
*(0*/*L[-*L*G]?*G/*(0*/*(*W*)*([a:z]*)/*(1*)*3*(2*)*)*)
*(0*/*3[a:z]?=*S[-*S*L*G]?*S*W/*(0*=*D*S *S*D/*)*)
*(0*/*(*3[a:z]?=*)*([-*W*D*S*L*G]?*)*(*W*)/*(1*)*D*(2*)*D*(3*)*)
*(0*/*3*(name=*)/*(1*)*)*(0*/*3*(href=*)/*(1*)*)*(0*/*3*(alt=*)/*(1*)*)
*(0*/*3[a:z]?=*D[-*D*L*G]?*D*W?/*)
*(0*/*L*F?em*W/*X*(0*)*)*(0*/*L*F?b*W/*X*X*(0*)*)*(0*/*L*F?strong*W/*X*X*(0*)*)
*(0*/*(*W?*)*(*Lh1*W[-*;*2]?*)/*(1*)*(2*=*0:*255*M *L*L *G*G *128:*191/*)*;
*(1*)*(2*=a:z-32/*)*;*(1*)*(2*=*0:*255*M *L*L *G*G *128:*191/*)*)
*(0*/*Lh2*W/Chapter: *(0*)*)*(0*/*Lh4*W/Subsection: *(0*)*)
*(0*/*Lh3*W/Section: *(0*)*)*(0*/*Lh5*W/Subsubsection: *(0*)*)
*(0*/*La*W*W?name=*(*D[-*D*L*G]?*D*)/*(1*) *(0*)*)
*(0*/*La*W*W?href=*D*(#?*)*([-*D*L*G]?*)*D/*Alt;*(1*=#*D/*)*(2*)*(1*=#*D/*)*Agt; *(0*)*)
*(0*/*L[a:z][a:z]?*W*W?alt=*D*([-*D*L*G]?*)*D/[*(1*)] *(0*)*)
*(0*/*L*F?[dou]l*W[-*L*G]?*G/*)*(0*/*Lli*W/o *(0*)*)*(0*/*Ldt*W/O *(0*)*)
*(0*/*Anbsp;/ *)*(0*/*L[-*L*G]?*G/*4*)*(0*/*1*W?*4[*W*4]?*2/*)*(0*/[*9 ]?*;/*;*)
*(0*/*Acopy;/(C)*)*(0*/*Aquot;/*D*)*(0*/*Aapos;/*S*)
*(0*/*Agt;/*G*)*(0*/*Alt;/*L*)*(0*/*Aamp;/*A*)
*(0*=*1:*4/*)*o/
*b*q/
*)*(*)*b*q2/"
The line breaks between the edit commands are for clarity and not in the actual command. (The actual command may be found in the asku source as batch file showidx[.bat].) All this is a single image activation, although the command line is a bit long. The first line above sets UTF-8 mode, (essential when using DOS). The second line searches for the <BODY...> tag indicating the end of the header and start of the text. When found, the third line starts a loop that provides the user a paging prompt to navigate the remainder of the html file. But first it resets the default search string to find sectional unit lines. The fourth to fourth-last lines above modify each html line before it is shown to the user. In particular, the fourth line puts markers *1 and *2 around each html line (see the *(0...*) TEMPLATE command), localizes Newline, and ensures a trailing space in html tags. The next line artificially closes broken html tags. The next three lines put a *3 marker before each html tag attribute and normalize the quoting of the value. The next two lines get rid of all attributes with values except the ones we want to look at. Next emphasis and bold are replaced by * characters. The "h1" document title is capitalized and shown between lines of hyphens. The line lengths ignore the trailing bytes in UTF-8 characters. (But you may want to skip capitalization if there are such characters.) Section headers are replaced by "Chapter:", "Section:", "Subsection:", or "Subsubsection:", depending on level. The names of anchors are shown between double quotes. Links are shown between angular brackets for potential cut and paste by the user. Alt strings are shown beween square brackets. The beginnings and ends of lists are converted into blank lines, provided they are on a line by themselves, and list elements are started with "o " Then all remaining html tags are removed, in the process totally removing all lines consisting of just tags and blank space. That is to get rid of excessive blank lines where html code was; blank lines without tags are retained as blank lines. Trailing whitespace is removed and some common html-encoded characters are unencoded. The third-last line gets rid of the markers and then shows the converted html line to the user. The second-last line closes the paging prompt loop and exits. The last line closes the loop that searched for the <BODY...> tag, returning error level 2 if the tag is not found.

If you write html source with emacs like I do, the results probably look OK, more or less. Especially if you adapt the html formatting a bit to improve the asku version. Use double quoting for anchor name values, so that the user can search for them exactly as they are listed in the <URL>. You might provide a help file on that using the asku HELP parameter. If you use some automated program to generate the html source, the results probably look like crap. The text should still be readable, anyway. If a tag extends over three or more lines, the inner lines will not be removed. Put the start and end of comment tags enclosing other tags on separate lines to ensure their removal.

Note the extensive use of nested edit sequences in the asku template above. A strategically placed extra *o/ can aid debugging. Good locations are immediately after the *1*c*2 line initialization, before the *(0*/*3[a:z]?=*D[-*D*L*G]?*D*W?/*) attribute removal line, and before the *(0*/*L[-*L*G]?*G/*4*) tag removal sequence. The most common errors I make are forgetting a /, or a ? in a search string, anyway.

Note that if the file has multinational characters in UTF-8 form, in DOS you will need to mess around a bit to get them to show up correctly. First, in the DOS window menu, (usually at the top left or right), you must select "Properties", then "Font", then change "Raster Fonts" into "TT Lucida Console" if it has not been done yet. The raster fonts have far too few characters. Then run the above command as follows:

chcp 65001
asku.exe "?index.html" - .....
chcp 437
Don't leave the code page at 65001, that makes DOS unworkable. That is particularly relevant if you want to run the above from a batch file. Earlier versions than Windows 7 will not read any batch files in code page 65001. You need to do it as, in DOS,
   call showutf8.bat
where the key part of showutf8.bat is
@echo off
set errlev=0
(
chcp 65001
asku.exe "?index.html" - .....
if errorlevel 1 set errlev=1
chcp 437
)
if not "%errlev%" == "0" echo asku returned an error
The parentheses are essential. Their contents is preread, so DOS does not need to read any more batch file before the code page is set back to 437.

Below are a few UTF-8 characters of increasing complexity, (i.e. length in bytes), for testing purposes:

     Spanish (Español)
     Greek (Ελληνικά)
     Russian (Русский)
     European Union (€)
     CJK (中日韓)
     High UTF-8 (𐑍𐑎𐑏)
For easier location of the above UTF-8, you may want to load this web page starting from the current section, as indicated by its name tag, and set the default paging search string to "high utf-8" too:
asku.exe "?index.html" - "*tu8/
*/*Lbody[*W*G]/*l/*n*(
*/*La*W*W?name=*Dcombine*D/*j+1/*l+0/*n*(
*/high utf-8/*l/*pp/
*z*1*c*2*(0*=*13/*)*(0*/*10/*;*)*(0*/*G/ *G*)
*(0*/*1*([-*L*G]?*G*)/*1*La *(1*)*)*(0*/*(*L[-*L*G*;]?*)*;/*(1*) *G*;*)
*(0*/*L[-*L*G]?*G/*(0*/*(*W*)*([a:z]*)/*(1*)*3*(2*)*)*)
*(0*/*3[a:z]?=*S[-*S*L*G]?*S*W/*(0*=*D*S *S*D/*)*)
*(0*/*(*3[a:z]?=*)*([-*W*D*S*L*G]?*)*(*W*)/*(1*)*D*(2*)*D*(3*)*)
*(0*/*3*(name=*)/*(1*)*)*(0*/*3*(href=*)/*(1*)*)*(0*/*3*(alt=*)/*(1*)*)
*(0*/*3[a:z]?=*D[-*D*L*G]?*D*W?/*)
*(0*/*L*F?em*W/*X*(0*)*)*(0*/*L*F?b*W/*X*X*(0*)*)*(0*/*L*F?strong*W/*X*X*(0*)*)
*(0*/*(*W?*)*(*Lh1*W[-*;*2]?*)/*(1*)*(2*=*0:*255*M *L*L *G*G *128:*191/*)*;
*(1*)*(2*=a:z-32/*)*;*(1*)*(2*=*0:*255*M *L*L *G*G *128:*191/*)*)
*(0*/*Lh2*W/Chapter: *(0*)*)*(0*/*Lh4*W/Subsection: *(0*)*)
*(0*/*Lh3*W/Section: *(0*)*)*(0*/*Lh5*W/Subsubsection: *(0*)*)
*(0*/*La*W*W?name=*(*D[-*D*L*G]?*D*)/*(1*) *(0*)*)
*(0*/*La*W*W?href=*D*(#?*)*([-*D*L*G]?*)*D/*Alt;*(1*=#*D/*)*(2*)*(1*=#*D/*)*Agt; *(0*)*)
*(0*/*L[a:z][a:z]?*W*W?alt=*D*([-*D*L*G]?*)*D/[*(1*)] *(0*)*)
*(0*/*L*F?[dou]l*W[-*L*G]?*G/*)*(0*/*Lli*W/o *(0*)*)*(0*/*Ldt*W/O *(0*)*)
*(0*/*Anbsp;/ *)*(0*/*L[-*L*G]?*G/*4*)*(0*/*1*W?*4[*W*4]?*2/*)*(0*/[*9 ]?*;/*;*)
*(0*/*Acopy;/(C)*)*(0*/*Aquot;/*D*)*(0*/*Aapos;/*S*)
*(0*/*Agt;/*G*)*(0*/*Alt;/*L*)*(0*/*Aamp;/*A*)
*(0*=*1:*4/*)*o/
*b*q/
*)*(*)*b*q3/
*)*(*)*b*q2/"
(Or use the showutf8.bat file that comes with the asku source files. For unix, source showutf8.)

I am unable to get the Lynx text browser to show utf-8 character in DOS. If this is a big issue for you, like it is for me sometimes, using asku instead could be an option to consider. Of course, any remote web pages would have to be fetched first with, say, wget.

Dealing with nontrivial DOS paths

Dealing with nontrivial file and folder names in DOS can be challenging. This is especially so if a path name includes multinational, (or more precisely non ASCII), characters. And if you want to open web pages in these paths.

Note first the following list of observations about DOS paths:

  1. Names should not just differ in case. They should not end in space or period. (Or start with a space, for that matter.)
  2. Characters *, |, \, :, ", <, >, ?, and / are illegal in file and folder names. So are control characters ASCII 0 to 31, (except in alternate data streams). However, ASCII 127, DEL, is treated as a normal character.
  3. Note that the special DOS characters %, ^, and & are allowed. The following example sets a variable L2HTOP to a potential installation folder %^&dir and then checks that the environment space has not been exceeded:
       set "L2HTOP=%%^&dir"
       if not "%L2HTOP%" == "%%^&dir" goto enverror
    
    Note that % must be doubled, and that the entire argument of the set command must be double-quoted (or ^ and & must be preceded by a ^ escape character). Other legal special file name characters like spaces and parentheses are OK in the above. Also note that you need double quotes in compound set commands, like in
       set "BINDIR=%L2HTOP%\bin"
    
    Otherwise two of the three problem characters above will cause problems. I would not know how to echo a general legal L2HTOP without enclosing it inside double quotes, which will then show up in the output. However, you could use, say,
       asku.exe - - "%L2HTOP%*;"
    
    instead.
  4. Character ;, while allowed and not normally problematic for DOS, would obviously prevent L2HTOP to be added to the PATH.
  5. There are significant problems in DOS if a file of folder name contains multinational (or more generally, non ASCII) characters. Suppose the current working folder name contains nontrivial non ASCII characters. Then the result obtained from the asku *w command on my United States Windows XP machine may contain three classes of multinational characters:
    1. Multinational characters represented by 8-bit characters that are correct assuming the Windows 1252 code page. Note that the result of the *w command is independent of the code page set in DOS; it is always 1252, even if the DOS codepage is, say, 437, IBM US ASCII, or 65001, UTF-8. Presumably, it depends on a "machine" code page. That would mean it could be different if your country is not the United States or Western Europe.
    2. Some multinational characters that cannot be represented in code page 1252 are, incorrectly, replaced by ASCII characters. For example, if a multinational character somewhat resembles a capital P, it may get replaced by a capital P.
    3. The remaining multinational characters that cannot be represented in code page 1252 are, also incorrectly, replaced by an (ASCII) question marks.
    The first type of characters does not seem to be a problem for asku, and will from now on be referred to as the "good" multinational characters. The latter two types of mangled characters are a big problem and will be referred to as the "bad" multinational characters. If a file specification contains bad characters, asku will not be able to find the file or open it.
  6. Assume, say, that the current folder contains bad multinational characters. Then if the 'cd', or 'echo "%cd%"' commands are redirected to a file or piped to asku, the result will depend on what code page is set in DOS. So in general the result will be different from what asku gets from the *w command. However, using %cd% within an asku template gives the same result as *w. The bottom line is not to redirect file specifications to asku, because asku may not be able to find or open the corresponding file even if all multinational characters are good.
  7. There is one exception to the rule not to pipe in multinational characters, good or bad. The exception is for when DOS code page 65001 is set. Normally redirecting, say, 'cd' to both a file and asku, asku receives the same string as the file does. However, for code page 65001, the current folder received by the file is the code page 1252 version (on my US machine). In the current folder received by asku from the pipe, the multinational characters are properly UTF-8 encoded as they should. No, you cannot open or search for the file with this string, but it allows a solid test for multinational characters (which might show up as ASCII otherwise):
    set errlev=0
    (
    chcp 65001
    cd | asku.exe - - "*/[*128:*255]/*n*(*q2/*)*(*)"
    if errorlevel 1 set errlev=1
    if errorlevel 2 set errlev=2
    chcp 437
    )
    
    The above sets variable errlev to 2 if there are multinational characters. (Do not forget the parenthetical grouping needed, extending from setting the code page to 65001 to resetting it. Or else.) The same trick is also useful for output purposes. For example, to show the current folder with multinational characters between square brackets, use
    (
    chcp 65001 | asku.exe - -
    cd | asku.exe - - "*z*c*(0*/[*192:*255][*128:*191]?/*1*(0*)*2*)*(0*/*2*1/*)*=*1[ *2]/*tu8/*o/"
    chcp 437 | asku.exe - -
    )
    
    (Simply printing *w to the screen will produce crap if there are bad characters, or even if there are only good ones but the code page of *w is not the same as the one set in DOS, as is usually the case.) Of course, you could easily combine the two asku calls above into a single one:
    set errlev=0
    (
    chcp 65001 | asku.exe - -
    cd | asku.exe - - "*/[*128:*255]/*n*(*X*X The current folder,*;
          *z*c*(0*/[*192:*255][*128:*191]?/*1*(0*)*2*)*(0*/*2*1/*)*=*1[ *2]/*tu8/*o/
    *q2/*)*(*)"
    if errorlevel 1 set errlev=1
    if errorlevel 2 set errlev=2
    chcp 437 | asku.exe - -
    )
    if "%errlev%" == "0" goto no_mult
    if "%errlev%" == "1" goto err_sub
    echo    contains multinational characters, shown between brackets above.
    
    (Omit the line breaks in the asku call.)
  8. Note also that if the "mangled" output of say the 'cd' command to the terminal screen is copied and pasted into a Windows Word or Notepad file, the correct characters miraculously reappear.
  9. It is not just asku and other DOS programs that have problems with bad multinational characters. Windows programs do. Consider my Windows XP machine with an old, 2005, version of Firefox, and the current folder, as shown in Windows,
       ~®غאָ %#&^;.!~@#$%^&()_+`-={}[];',.®©Đύьغ₧ﺕאָ
    
    In DOS, this folder had a number of bad multinational characters, both ones that show up as question marks and ones that show up as incorrect ASCII characters. Started from DOS, Firefox could not open web pages in it. As another example, I created two folders, each with a name of the form C:\try\XXXX where each X was a multinational character that shows up as a question mark in the mangled version. In other words, %cd% or equivalent produces the same incorrect path C:\try\???? for both folders. Doing a cd into one of the two folders, Firefox was not able to open an html file "a b.html" using a legacy url:
       start firefox "file://%cd%\a b.html#download"
    
    It was not even able to open the web page if inside the C:\try\XXXX folder, you used
       start "" "a b.html"
    
    and Firefox was the default browser. Firefox still ended up with full file specification "C:\try\????\a b.html", which is invalid. The only solution was to switch to the "short" DOS path name, as described below. It may be noted that Firefox also failed to open files if there were good international characters, or DEL characters, in the folder name, but in this case only if Firefox was already running. A new instance had no problems there. This old Firefox also did not eat the ASCII characters #, %, and ; unless they were "percent encoded" as per the URL/URI specification. Note that ; is suppressed in DOS "short" file names, but % and # are not. (In new 2014 versions of Firefox and Chrome, only # is still a problem.) But also note that #, %, and ; in the path name were OK if you opened the web page like
       start "" "a b.html"
    
    and Firefox was the default browser. However, also note that good international characters, or character DEL, in the path were now not OK if Firefox was the default browser and it was already running. (This has been fixed in recent versions of Firefox.) For a command like
       start firefox "a b.html"
    
    Firefox would try to open a web page http://www.a b.html. (This has been fixed in recent versions of Firefox, but still applies to Chrome.)
  10. Unlike the old version of Firefox,
       start iexplore "file://%cd%\a b.html#download"
    
    worked whatever the characters in the path. But given the expanded DOS command line, (as obtained by, say an echo command or redirection)
       start iexplore "file://C:\try\????\a b.html#download"
    
    Internet Explorer too cannot open the file. On the other hand, given the unexpanded line, Internet Explorer will "correctly" open the file if it is in the current folder, but not if it is in the other folder. Yet the expanded line is identical in both cases. Similarly
       dir "%cd%"
    
    will "correctly" list the files in the current folder C:\try\XXXX, but its expanded form
       dir C:\try\????
    
    will correctly list all files and folders in folder C:\try that have a four character file name and no extension.
  11. It appears therefore that in its internal mechanics, DOS uses unique international characters (UTF-16LE?). However, when it gives them to files or to DOS applications like asku, it mangles them according to some code page. Note that if asku gets two folder names both as ???? and no more information, there is no way to compensate for the problem. Even called from DOS, a Windows program like Internet Explorer is apparently somehow able to access the non mangled version of its parameters that DOS use internally.
  12. Recent, 2014, versions of Firefox and Chrome also appear to know the trick used by Internet Explorer. However,
       start  "file://%cd%\a b.html#download"
    
    will fail for these browsers if there is a # in the path. In that case, if there are no bad multinational characters, you can percent encode the URL using asku, provided you encode all multinational characters. If there are bad multinational characters, the only trick I would know is the next item.
  13. One possibility to circumvent problems with multinational characters is to switch to the alternative DOS "short" path name. Here file and folder names are limited to 8 ASCII characters, plus three more for the file extension. However, there are some pitfalls with this approach:
    1. Short file names can be disabled in recent versions of Windows. Hopefully, not many people will actually do so. There is supposedly a small performance improvement, but I understand it is negligible.
    2. The command "command /c cd" will conveniently change %cd% into its short form, at least on XP. However, recent versions of Windows like Windows 7 seem to come without command.com.
    3. A parameter expansion like %~sdp1 or %~sf1 would in principle be ideal for getting the short path name. However, it is buggy in at least my version of Windows XP. As an example, for the folder
            C:\try\!~@#$&()_+`-={}[];',.0123456789abcdefghij
      
      containing the file "a b.html", given as third parameter of a batch file, "%~sf3" produces
           "C:\try\!~@#$&~1.012\AB5564~1.HTM456789abcdefghij\a b.html"
      
      Note that the length of this string has not been correctly truncated; the end of the original long path is sticking out, starting from 4567... (Interestingly enough, for file "aaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa.html" you do get correctly
           "C:\try\!~@#$&~1.012\AAAAAA~1.HTM"
      
      even though both file names are nominally long.) (Note also that I have never observed the %~s... truncation bug above if the final filename is a DOS 8+3 one, as described below.)
    4. Expressions like %~snx produce the long file name and extension, not the short one.
    To prevent a short version of a file or folder name being created, the root name should be 1 to 8 legal ASCII characters long, and the extension from 0 to 3 long (the DOS 8+3 format). There should not be any multinational characters in name or extension, nor spaces, additional points, nor any of the characters comma, [, ], ;, =, or +. (The latter 6 characters, if present near the start of a filename would show up as _ in the short filename. Multinational characters and spaces are ignored in generating a short filename. while the effect of additional points varies.)
  14. The compiled Windows binaries of the Lynx browser, as of June 2014, will not display multinational characters in UTF-8 form using DOS codepage 65001. This is apparently because the characters are send to output byte by byte, rather than as a complete character. Note also that lynx (2.8.7rel2) may fail if a full file specification is provided that contains \~\ or a # character. (The \~\ will be replaced by whatever lynx thinks is your home folder. Even if it is not even at the start of the path. Like for Firefox and Chrome, anything after a # is taken to be anchor and not filename.) Astonishingly, even if you provide a file:// URL that is fully percent encoded according to the URL/URI standard, these characters still cause failure. Lynx does seem to be OK if you cd to the folder with the file and then provide a bare file name, followed by a #, even if there is no anchor. At least, it is OK as long as the current folder name does not contain bad multinational characters. In other words, lynx seem fine if you use the short filename path to cd to and then a bare filename, followed by some anchor.
  15. Warning: Do not forget that the command lines of call statements are decoded twice, so that % characters must be doubled. For example,
       call "%%L2HTOP%%\bin\showhelp.bat" %%1 "%%L2HTOP%%\help\intro.htm"
    
    Without the doubled quotes, a % or ^ in L2HTOP or parameter 1 would cause failure.
  16. Warning: Note that if a ^ character is explicitly present in a quoted batch file argument, the batch file will end up with two copies. For example, in
       call script.bat "%%%%5 ^"
    
    script.bat will end up with a first parameter "%5 ^^". (This is presumably due to an imperfect fix for a non-quoted ^ disappearing in the double decoding.) If you want to transfer the quoted argument "%5 ^", the way to do it is
       set "U=^"
       call script.bat "%%%%5 %%U%%"
    
    The following asku call creates a batch file tmp.bat that calls script.bat with as argument whatever is echoed to asku:
       echo "..." | asku.exe - "?tmp.bat" "set *DU=^*D*;call script.bat *z*f*(0*=*H*H*H*H*H ^*H*HU*H*H/*)*o/*;"
    
  17. Warning: If the name of a batch file starts with @, it must be double quoted when called:
       call "@hlptmp_.bat"
    
    Or else.
  18. Warning: internal double quotes in strings are often not handled as expected, including by %~ expansions and by asku. Try quoting entire strings, rather than individual parts of it. Instead of "%L2HTOP%"\bin\"a b.html", use "%L2HTOP%\bin\a b.html"

As an example, consider how I check the installation path of a software package of mine. Inside my install.bat script, I have the asku call (line breaks are for clarity and not in the script)

asku.exe - "?tmp.cd"
 "*z*w
*k/*o/\install.bat*v?/*(*)*(*q3/*)
*k0/*@/\*v0?/*(*q3/*)*(*)
*~i?/*qe3/*c*qe1/
*/Leon van Dommelen/*l/*n*(
*k/*o/*(0*/[*0:*31*D#*H*X;?*127:*255]/*)*k0/*o/*ve/*(*)*(*q2/*)
*.*qe4/*o/*;*q/
*)*(*)*b*q3/"
The second line above puts the installation (current) folder in the gather buffer. The third line checks that install.bat is in it. To understand the reason for doing that, recall that multinational characters in the path may be incorrectly replaced by question marks or ASCII characters. In the former case *w will be a nonexisting folder, in the later case, *w might even be an existing, but wrong folder. To make even more sure we have the correct *w in the buffer, the fourth line checks that the found install.bat is not a folder. (To check whether something is a folder in a DOS cmd.exe window, append a backslash and check that it still exists.) The fifth line checks that we can open the file. Note the use of *qe3/ to return a level 3 error status, rather than the default 1, when something goes wrong in opening the file and reading it. The sixth line checks that the file has my name in it at least once. By now, the chances that I still have hold of the wrong installation path must be extremely small. I do not believe this is a problem I will be seeing much, even optimistically assuming tens of millions of users.

Now that I am sure I have the correct path, in line 7 I check it for unacceptable characters. Note that the installation path is to be stored in a variable L2HTOP, to be used inside various DOS and other scripts. As for the unacceptable characters, *0:*31, double-quote, and * should not appear in normal DOS paths anyway, and ? only if the path is wrong. Character ; must be disallowed because it prevents the installation folder from being added to the DOS path. As an additional constraint, I want the installation path to be such that I can open web pages inside it with Firefox or similar, and do so without percent encoding even if an anchor is attached. (In DOS, percent encoding comes down to creating a temporary batch file, which is obviously ugly.) That also disallows #, % (unless you live dangerously), the already disallowed ; and ?, DEL, and multinational characters. Even if multinational characters are not mangled by DOS, I have no real clue how to safely encode them in a file:// URL. The official line seems to be to percent-encode the UTF-8 bytes. However, this conflicts with, for example, what is said in http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx. Also, assuming that there are only good multinational characters, percent encoding the 1252 "machine" code page bytes works even with 2014 versions of Firefox and Chrome, not just 2005 Firefox or 2009 Lynx. On the other hand, percent encoding the UTF-8 bytes also works with 2014 versions of Firefox and Chrome. (I did not try the other two.) So what is right? Staying clear of multinational characters altogether seems the safest bet. Further, character * in the folder path, already disallowed by DOS, is also disallowed so that I can use variable L2HTOP inside an asku template. Control characters, already disallowed by DOS, are also disallowed because I use them as markers in editing batch script files (encoding them every time would be a pain). If there are unacceptable characters, I exit with error status 2.

If all is well, in the second-last line I write the found path name to file tmp.cd, returning error status 4 if the disk is full or similar. The last line closes the earlier name search; I exit with status 3 if my name is not in the file. I think the above asku template is reasonably concise, given the amount of work it does. Think of doing the same thing with Unix utilities, even assuming you have them installed in DOS. And with asku, there is just one image activation.

If the above asku command fails to produce a usable installation folder path, I try switching to the DOS short path name, using

    call cdshort.bat install.bat "Leon van Dommelen" .
Here cdshort.bat is a batch file that does a "cd" to the short path as, in the simplest case,
   cd "%~sdp1"
where %~sdp1 is the path of its first argument, install.bat. The second argument of cdshort is again the string that must be in the file, to verify its ID. The third argument is a folder in which cdshort can put a temporary batch file if the above cd command does not work.

Batch file cdshort.bat (which can be found in the asku source distribution) requires that if no temporary folder is supplied, its first argument is a DOS 8+3 filename. (Recall the truncation bug mentioned above.) Cdshort checks that with the following asku call (after checking that the file exists, so is a legal DOS file name):

asku.exe - -
"*z:%~1:*~ig/
*/:[-:.,*0:*32*O*R;=+?*128:*255][-:,*0:*32*O*R;=+?*128:*255]?:/*n*(
*/:[-.][-.][-.][-.][-.][-.][-.][-.][-.:]/*n*(*q2/*)*(
*/.[-.]?./*j-1/*n*(*q2/*)*(
*/.[-:][-:][-:][-:]/*j-1/*n*(*q2/*)*(*)*)*)*)*(*q2/*)"
The second line puts the file name in a virtual input file surrounded by colons. The third line checks that the individual characters are not invalid. The fourth line checks that the name is no longer than 8 characters. The fifth that there is at most one point (there might be none). The sixth that the file extension is no longer than three characters. If anything is wrong, error status 2 is returned.

If %~sdp1 fails to set the short path, presumably because of the mentioned truncation bug, cdshort tries to get the short path by taking the full short file specification and stripping off the bare file name, %~snx1 and the trailing junk characters. However, as mentioned, %~snx1 produces the long bare file name, not the short one. The only way to get the short bare file name seems to be to take it from column 5 of the DOS "dir /x" command. The key asku call is therefore:

dir /o/x "%~1" | "%L2HTOP%\bin\asku.exe" - "?%~3\cdtmp_.bat"
"*/*10[-*W]/*z/*l/*n*(*1*c*j0?/*)*(*)*b*k0/%~1
*(0*/*1[- ]?  ?[- ]?  ?[- ]?  ?[- ]? *([!:*127][!:*127]?*)  ?*i*?*;/*10*(1*)*)
*(0*/[-*10]?*10/*)*k0/*o/*v0n/*(*k0/%~1*)*(*)
*z%~sf1*X*(0*/*i\*?*X/*)*k/*o/\%~1*v?/*(*)*(*(0*/*i\*?[-]?/*)*k/*o/\%~1*v?/*(*)*(*q2/*)*)
*k0/*@/\*v0?/*(*q2/*)*(*)
*~i?/*qe3/*c*j0/*qe1/*/%~2/*l/*n*(
*.*qe3/cd *D*(0*=*H*H*H/*)*o/*D*;*q/
*)*(*)*b*q2/"
The second line searches past the directory header for the line with the short filename. If found, it puts it in the gather buffer preceded by *1. The line also puts the long bare filename in storage location 0, *?, so that it can be used in search strings. (Using %~1 directly in a search string would create havoc if it contained a [.) The third line takes the short bare filename from column 5, (if it exists, i.e. if there is exactly one space behind column 4), and puts it behind the *10 line end. The fourth line then strips off everthing to the line end. That leaves the bare short filename, if any, or nothing, if none. The line then sets storage location 0 equal to the short file name, if any, or the long one, if none. Line five first tries to strip off the final bare file name and preceding slash from the full path %~sf1. If that does not produce the desired path, it tries to strip off the first bare filename, preceding slash, and following junk. If either is successful, the final lines check that file %~1 is indeed in this path, and if it is, create a batchfile cdtmp_.bat that does a cd to it. Note that % characters must be doubled in the path string.

No generic utility other than asku could do all this in a single image activation. Not to mention that no input files are used. The entire "program" is a long, but single command line.

Another problem arises when I need to open a web page in the user's working folder, instead of in the l2h folder. Obviously, then I have no control over the path to the web page. The best I can (normally) do is switch to the short path name, to at least get rid of multinational problem characters. (Non mangled multinational characters are OK. But if there are mangled ones, only Internet Explorer or asku.exe will work for sure.) Then I will open the web page as, say,

   start firefox "%cd%\a.htm"
if there is no anchor and no DEL in the path. Alternatively, I will use, say,
   start firefox "file://%cd%\a.htm#adv"
if there is no # or ; in the path. If all else fails, I create a batch file hlptmp_.bat that starts Firefox with a percent-encoded true file:// URL. This can be done as
(
chcp 65001
echo "%cd%\a.htm"| "%L2HTOP%\bin\asku.exe" - "?hlptmp_.bat"
 "start firefox *Dfile:///*z*f
*(0*=*W:)*H2+16 *D *X:,*H2+23 ;:?*H3+7 @*H40 [:*U*H5-25 \*F *K*H60 {:*127*H7-57 ~~
 *128:*137*H*H8-80 *138:*143*H*H8-73 *144:*153*H*H9-96 *154:*159*H*H9-89
 *160:*169*H*HA-112 *170:*175*H*HA-105 *176:*185*H*HB-128 *186:*191*H*HB-121
 *192:*201*H*HC-144 *202:*207*H*HC-137 *208:*217*H*HD-160 *218:*223*H*HD-153
 *224:*233*H*HE-176 *234:*239*H*HE-169 *240:*249*H*HF-192 *250:*255*H*HF-185/*)
*o/#adv*D*;"
chcp 437
)
Note that currently asku has no elegant way of percent encoding; you just have to crunch it out. The fifth line above percent-encodes all possible ASCII characters as per the URI/URL specification, leaving characters that should not be encoded unchanged. It also removes the double quotes used to protect the echo command parameter. The next four lines percent-encode all 8 bit bytes, if any. Otherwise multinational characters will not be received correctly by Firefox. If you assume that you can always set the short path, (but as noted, that may not be the case), these four lines could be omitted. Note also that percent encoding all ASCII characters is overkill and leads to an ugly URL. I prefer just to encode the potentially troublesome ones, replacing the fifth line by
*(0*=*D #*H*H23 *H*H*H25 ;*H*H3B \*F

For Lynx, the command to use is, (in the absence of mangled characters in the path),

   call lynx.bat "a.htm#adv"
where lynx.bat is a suitable batch file to run lynx. Even there is no anchor, leave the #.

Dealing with nontrivial Unix paths

The biggest problem with Unix/Linux paths is that everything except the folder separator / and NUL is a legal path name character. The good news is that no Unix user would actually use anything else than lowercase letters. The folder "Ubuntu One" is a rare exception, and one which will cause your foreach loops to fail. Delete it.

Note first the following list of observations about unix paths:

  1. As mentioned, everything except / and NUL is legal in file and folder names. Even Newline (equal to Linefeed) is. Two particularly annoying characters are ! and '. Suppose an installation directory is described in a variable L2HTOP, and contains one of those characters. Then the next generic tcsh command using back-ticks,
       "$L2HTOP/bin/prog1" "par=`'$L2HTOP/bin/prog2' '$L2HTOP/data/data.dat'`"
    
    will fail.
  2. Character :, while allowed and not normally problematic for Unix, would obviously prevent L2HTOP to be added to the PATH.
  3. A further problem is the obnoxious Linux .desktop file specification. This specification is very limiting if you want to use .desktop files to open programs that are not in the path. And since modern Linux distributions like Ubuntu now refuse to open double-clicked scripts, you have little choice here. The .desktop specification disallows non ASCII characters, control characters, and even the plain = character.
    In a .desktop file Exec line, the program name should be enclosed between double quotes. Do not use additional single quotes within the double ones. In addition, for some special characters, the following encodings should be used:
    % \ " ` $
    %% \\\\ \\" \\` \\$
  4. Firefox does not eat the ASCII characters #, %, ;, and ? in a file:// URL unless they are "percent encoded" as per the URL/URI specification. Note that Firefox will open an html file if you simply specify the file, but that does not allow you to attach an anchor. Leaving multinational character bytes unencoded seems to work fine, at least on my modern Ubuntu versions. (The URL/URI specification says multinational characters must be converted to UTF-8 and then the bytes percent-encoded. Unfortunately, Unix file systems do not have an encoding; path names are sequences of bytes, not characters. Someone may want to put a locale-type interpretation on top of that, but I do not know which one nor how to handle it. Instead, I assume that if I give Firefox a local file URL with unencoded 8-bit characters, it will do the sensible thing and simply open the folder as specified. My Ubuntu versions with C locale seem to assume UTF-8 encoding of path names anyway. However, this may not be true on other Unix versions, especially old ones, or non C locales.)
  5. Chromium is much like Firefox, except that in a file:// URL, it will replace \ by /. So \ must be encoded too. But ; is not a problem.
  6. For either browser, if an instance is already running, it will be used and execution continues. If no instance is running, a new one will be created and execution pauses until the browser is exited. To avoid such inconsistent behavior, you can always run the browsers as a detached process. They do not return a usable error status anyway.
  7. For Lynx 2.8.8dev9 (Jun 2011), the command
       lynx -term=vt100 "`pwd`/index.html"
    
    will open file index.html if it is in the folder
       bd~!@#$%^&*()_+`-={}|[]\:";'<>?,.
    
    with problematic ASCII characters, or if it is in the folder
       mlñλРу€中日韓𐑍𐑎𐑏
    
    with problematic UTF-8 characters. However, astonishingly, if it is in a folder whose name is a combination of the two above, the command will fail. And that is not because the combination name is too long. The command
       lynx -term=vt100 "`pwd`/index.html#download"
    
    will work in the multinational folder, but not the other two. It will also not work in a folder simply called '~'. Using a percent-encoded file:// URL, leaving the multinational characters unencoded, works fine in every case on my Ubuntu system. The simplest method, like in DOS, seems to be to cd to the folder with the file and then use a relative file specification:
       lynx -term=vt100 "index.html#download"
    
    No # is needed if there is no anchor.

As an example, consider how I check the installation path of a software package of mine for unacceptable characters. The installation path is to be stored in a variable L2HTOP, to be used inside various tcsh and other scripts. Inside my install.bat script, I have the asku call (line breaks are for clarity and not in the script)

asku.exe - "?@tmp.cd" "*z*w
*k/*o/*(0*/[*0:*31*E*S*X:=*127:*255]/*)*k0/*o/*ve/*(*)*(*q2/*)
*.*qe3/*o/*;"
In the second line, I exit with error status 2 if the installation (current) path contains characters that I do not accept. Control characters, non ASCII characters, and = cannot be accepted because of the desktop specification, period. (I also tend to use control characters as markers, and encoding existing ones would be a nuisance.) Character : cannot be accepted because the folder must be added to the PATH. Further, character * in the folder path is disallowed so that I can use variable L2HTOP inside an asku template. Characters ! and ' are disallowed so that I can use L2HTOP inside back-tick commands. Character ' would further be a nuisance for using a single-quoted L2HTOP inside perl scripts. (This is less annoying in DOS, as proper DOS paths do not end in \.)

I think the non-desktop, non-PATH, restrictions above are minor, and make my life a lot easier. As noted, most Unix users use just lowercase letters anyway. And Unix users who use single quotes or the wildcard * in file names are used to things not working as expected. That leaves as only concern potential nasty e-mails from users who would use exclamation marks in file names. But I can filter for the number of exclamation marks in my incoming e-mails.

Another problem arises when I need to open a web page in the user's working folder, instead of in the l2h folder. Obviously, then I have no control over the path to the web page. If there is no anchor attached, I simply specify the file in the firefox, chromium-browser, or xdg-open commands. If however there is an anchor, I do a full percent encoding of the file path *w/$3 in the file:// URL. (I do have control over the file name, call it index.html' and the anchor, call it #download, so these will be free of problem characters.) The Firefox command then becomes:

firefox "file://`asku.exe - -
 '*z*w/index.html*(0*=*0:*9*H0+48 *10:*15*H0+55 *16:*25*H1+32 *26:*31*H1+39
 *W:)*H2+16 *X:,*H2+23 ::?*H3+7 @*H40 [:*U*H5-25 *K*H60 {:*127*H7-57 ~~/*)*o/'`
#download" &
The back-tick command percent-encodes all characters as per the URL/URI specification, except 8 bit ones. In particular, the second line encodes control characters 0 to 31, and the fourth line all other ASCII characters that should be encoded. Note that using the back-tick command, there is no need to explicitly create a temporary file like in DOS.

General Usage of Asku

The general format of an asku call is:

    asku.exe INPUT OUTPUT TEMPLATE* TYPE DEFAULT HELP* MAX P8 SEP*
The parameters marked with a * must be star-encoded as described below. In DOS, the .exe can be left. Also in DOS, avoid internal double quotes in the parameters. Try double-quoting the entire parameters, rather than individual parts of it. In particular, consecutive double quotes will produce failure. This is an effect of how DOS or the GNU compilers parse the command line and out of asku's control.

Details on the parameters above are given in subsequent sections, but here is an overview:

Trailing spaces in parameters are ignored, so " " is a nil parameter. A nil parameter may more briefly be specified as a - hyphen. At the time of writing, all arguments can be up to 256 characters long, except that TEMPLATE can be up to 32,768 characters long.

To simplify entering special characters, and to enter more complicated TEMPLATE expressions, "star-encoding" must be used for various parameters. The rules are:

  1. A * character must be entered as *X if the parameter uses this encoding.
  2. Characters are encoded using a * followed by a capital. A list of such encodings, with what they encode below them, is:
    *A *B *C *D *E *F *G *H *I *J *K *L *M *N *O *P *Q *R *S *T *U *V *W *X *Y *Z
    & \ : " ! / > % $ ` < - [ + ? ] ' Tab ^ | Spc * Sep
    Here Tab stands for a tabbing character, Spc for a blank space, and Sep for the folder separator in paths (\ for MS DOS but / for Unix).
  3. Any 8-bit character may be encoded as *nnn where nnn is the three-digit decimal character number from 000 to 255. Leading zeros may only be omitted if nnn is not followed by a possible digit. So *25C and *256 are Ctrl+y (character 25) followed by C, respectively 6. However, Ctrl+y followed by a 5 must be written as *0255.

Warning for Microsoft programmers who use standard output: on Windows g77 will precede every linefeed character by a carriage return one. To compensate somewhat for this stupidity, I suppress every carriage return unless I see that the next character is not linefeed. That mainly means that you cannot create standard output with unix-style newlines. And in the unlikely case that a carriage return is the last character of standard output, you need another one. These things are not a concern if you output to a file or use Unix.

INPUT

INPUT is the source of input: the user, a file, or standard input. It is not star-encoded.

The various possibilities for INPUT are:

-
If the input is from standard input (i.e. piped in), specify INPUT as - or " ". Note that in this case, all user interaction is impossible. Also note that input lines are truncated, by default to no more than 256 8-bit characters. (At the time of writing, this may be increased to 8192 characters using the *ti.../ command.) Additionally, after truncation any trailing spaces are removed. Also, manipulations in which asku must go back to earlier input lines are limited by the buffer it has to save these earlier lines. Uses arguments INPUT-TEMPLATE.

"?FILE"
If the input is from a file called FILE, specify INPUT as "?FILE". User interaction for paging the output or other is possible. Should be a text file, though a binary file might work if its longest "line" fits in the asku input buffer. Uses arguments INPUT-TEMPLATE or more depending on application.

TASK
If the input comes from the user, INPUT must be specified as one of the following user tasks:

color:
Ask the user to input a color. Uses arguments INPUT-HELP.

The user's input can be output as *? in TEMPLATE. By default, this gives the color in the form HHHHHH, where the HH stand for the two hex digits of red, green, and blue respectively. However, the *: command allows you to put a divider between the pairs of hex digits. It also defines the *@/ command to give the three colors as a decimal fraction from 0 to 1, separated again by the divider.

folder:
Ask the user to type in an existing absolute folder specification. Uses arguments INPUT-HELP.

The folder provided by the user can be output as *? in TEMPLATE. It will never end in a path divider. In particular, the top folder will end in "/." or "\.". This simplifies adding a file name at the end.

menu:
Ask the user to select an item from a list by pressing a key. Uses arguments INPUT-SEP.

Selectable items can be up to 79 characters long, but they may be shown truncated in the user menu. More specifically, if there are no more than 17 items, up to 75 characters are shown, if no more than 35 items, up to 35 characters, otherwise up to 22. For more than 51 items, multiple menu pages are used. The number of items cannot exceed 459.

The list of items for the user to select from must be in a file FILE:r.lst where FILE:r is the OUTPUT file with its extension removed. Lines cannot be longer than 256 characters.

It is possible to restrict the selectable items to a subfield of the lines in the input file OUTPUT:r.lst. For that purpose, each line in the input file is considered to be of the general form:
    FIELDN SEP ... SEP FIELD3 SEP FIELD2 SEP FIELD1
where the spaces are for clarity and should not actually be in the input file. Then you can make, say, FIELD2 the selectable item by specifying asku parameter P8 as 2. (If FIELD2 is of zero length, it is ignored, but entire lines of zero length are a fatal error.) If you specify -2 instead of 2 for P8, you get the entire string from FIELDN to FIELD2, (including the separators that are in it.) You can define what characters are separators SEP by including them in the SEP parameter of asku. For example, if you want to separate the parts of a full file specification, in DOS you would specify SEP as "\:", but in unix as "/". (In the DOS case, do not put the \ immediately before the closing double quote. Leave a space in between, or specify the \ as *B instead. Also remember that if * is a separating character, it must be encoded as *X.) For a full file specification, P8 equal to 2 would give the folder in which file FIELD1 is located. (Or the DOS disk letter if there are only two fields.) P8 equal to -2 would give the entire path to the file. If P8 is missing, blank, or 0, the entire line is the selectable item. If P8 is a nonzero number, but SEP is blank or missing, Space is taken to be the separator. If SEP includes a SPACE, specify it as the first character and be sure to use double quotes.

The item selected by the user can be output as *? in TEMPLATE. Also, if asku argument MAX is specified as a positive number, *@/ in template gives the selected item padded or truncated to exactly MAX characters. IF MAX is a negative number, *@/ in TEMPLATE gives the selected item truncated to no more than -MAX characters. Otherwise *@/ is undefined.

number:
Ask the user to input an integer number. Uses arguments INPUT-P8.

If MAX is defined and not blank, it is the maximum number the user can enter. If P8 is defined and not blank, it is the minimum number the user can enter. If P8 is blank or missing, the user can enter negative numbers of arbitrary magnitude.

The number provided by the user can be output as *? in TEMPLATE.

string:
Ask the user to input a string. Uses arguments INPUT-P8.

If MAX is defined and not blank, it is the maximum number of characters the user can enter. (That are 8-bit characters; UTF-8 characters beyond ASCII are not really accommodated, sorry.) If MAX is missing or blank, the number of characters will be limited by the prompt line length. It is recommended that the prompt line length plus MAX add up to exactly 77 characters. This will automatically prevent the user from entering strings that are too long. If P8 is defined and not blank, it is the minimum number of characters the user can enter. If P8 is zero or missing, the user can provide a nil string.

The string provided by the user can be output as *? in TEMPLATE. Control characters will be refused, and the user input cannot be q, as that quits. Leading and trailing blanks will be omitted.

value:
Ask the user to input a floating point number. Uses arguments INPUT-P8.

If MAX is defined and not blank, it is the maximum value the user can enter. If P8 is defined and not blank, it is the minimum value the user can enter. In either value is followed by an x then that exact value is illegal too. For example P8 equal to 0x accepts small positive numbers but not 0 itself. MAX and P8 should contain no more that 6 significant digits. And exponential notation is not supported.

The number provided by the user can be output as *? in TEMPLATE.

The above tasks can be truncated to a single character c, f, m, n, s, or v. Note that these commands assign standard input to the user, so no additional input can be piped in. (To be precise, a simulated user response could be piped in. In that case, you would want to make sure that OUTPUT is a file, so that the asku user prompts can be redirected to a scratch file, NUL, or /dev/null.) If you also want to provide an input file, see the TEMPLATE section for the *a.../ command. This frees up the INPUT parameter, allowing it to be assigned to a file.

OUTPUT

OUTPUT is the output device, either a file or standard output. It is not star-encoded.

For standard output, specify OUTPUT as - or " ". But note that standard output is also used to prompt the user. So standard output cannot be redirected if any user input is needed. Also, there will be no clear sign that the user has quit.

Else the output device must be a file for asku to write to. This file should not exist yet, but that is not checked. If the file is called FILE, then OUTPUT should be specified as "?FILE". If the user quits, file FILE is not created and the exit status is 0.

Note: If you specify OUTPUT as "+FILE", then the output is also copied to standard output. So then you get a record of what was on standard output, (excluding user inputs and prompts and such). This is like the unix "tee" command. May be useful inside pipes or to keep a log of something.

TEMPLATE and its Commands

Intro

TEMPLATE specifies what output is produced. It is star-encoded, so you must use *X if you want to output a star, *, character. You should also make it a habit to encode the forward slash, /, by *F, as the forward slash is used as a terminator by many commands.

TEMPLATE consists of characters you want to output and commands. For example, if TEMPLATE equals

   "*X*X*X Hello World *X*X*X",
asku will output the string *** Hello World ***. But this line will not be terminated by going to the next line. A line terminator is produced by the *; newline command:
   "*X*X*X Hello World *X*X*X*;",

If TEMPLATE ends in a * character, then that * will be removed. This is useful to preserve trailing spaces which otherwise would be lost. For example, to write three blank spaces to the screen without going to the next line, (to indent whatever you write to the screen next), use the following asku command:

   asku.exe - - "   *"

TEMPLATE commands

In addition to simple characters, you can use commands in TEMPLATE. A list of them now follows.

*;   *?   *@NUM/   *.   *:DIV/   *#DIGS/   *=.../   */SEARCH/   *(0*=CHARREPS/*/SEARCH/REPLACE*)   *~iCNUM/ or *~oCNUM/   *_NAME/   *aITEMTYPE HELP DEFAULT MAX P8 SEP/   *b   *c   *d   *eITEMDIGIT   *f   *g   *h   *i   *jLINESPEC/   *kNUM/   *lLOW:HIGH/   *m   *n*(FOUND*)*(NOTFOUND*)   *oLINES/   *p.../   *qSTAT/ or *qeSTAT/   *rLOC/   *s   *t.../   *uCHRNO/   *vLOC1LOC2TASK1TASK2TEST/*(YES*)*(NO*) or *vLOC1LOC2TASK1TASK2/   *w   *xLOC/   *y   *z

More details below:

*;
Generates a newline character sequence.

*?
Outputs the user input, if any, as described more fully in section INPUT.

*@NUM/
Outputs the string kept in storage location number NUM. Note that *? above is simply an alias for *@0/. Further *@/ is an alias for the last storage location, as defined when asku was built (16 at the time of writing). In other words, the default for NUM is the last available storage location. Part of the *@, *., *g, *k, *o, and *z commands.

*.
Terminates gathering or keeping of the TEMPLATE output and sends the following output again to the normal output device. Part of the *@, *., *g, *k, *o, and *z commands.

*:DIV/
Assuming that the user has entered a color in the color TASK or *ac.../ command, redefines *? as HHDIVHHDIVHH, where the HH are the red, green, and blue values in two-digit hex form. Also defines *@/ as REDDIVGREENDIVBLUE, where RED, GREEN, and BLUE are the color values as a decimal fraction from 0 to 1.

*#DIGS/
Outputs the next input line number. If DIGS is missing or zero, simply outputs the number. Otherwise the number is zero-padded or truncated at the left to exactly DIGS digits.

*=.../
A *=.../ command allows you to replace 8-bit characters by strings. Normally the replacement is done when the characters are send to the output device, not if they are send to the gather buffer. However, if the *=.../ command is inside a *(0...*) edit string, as discussed below, the replacement is applied to the gathered data.
The ... in *=.../ must consist of space-separated entries of the form CSSS or C:cSSS. Here C and c are 8-bit characters and SSS is a string of replacement characters. In the CSSS form the replacement applies only to character C, in the C:cSSS form, the replacement applies to all characters from C to c. If SSS includes a string of the form +nnn or -nnn, with nnn a number of up to 3 digits, it is taken as a character shift. For example, *=A:Z+32/ converts to lower case.
There is one other possibility for SSS different from what is described above. In this case SSS is of the form *unnnn where nnnn is an number. Then the replacement string is a unicode character (a single unicode character consists of several 8-bit characters). If nnnn does not start with a sign, the unicode character number is nnnn. If nnnn starts with a sign, then the unicode replacement character number is the character number for the original 8-bit character plus nnnn. That may be convenient when converting a series of 8-bit characters to unicode when unicode has these characters in the same order. In particular, the popular Western European ISO 8859-1 8-bit character set can be converted to unicode on output using *=*128:*255*u+0/. Note also that the asku template length allows up to 32768 8-bit characters, (at the time of writing), so it is possible to convert 128 8-bit characters to unicode by specifying each separately in the template.
See the *tu8/ command below for important info on using UTF-8 in DOS.
Any * star in CSSS or C:cSSS must be encoded as *X, any / forward slash as *F, and any Space as *W. Any + in SSS that is not part of a character shift must be encoded as *P, any such - as *M. Any : at the start of SSS in CSSS must be encoded as *C. Arbitrary 8-bit characters may be encoded as *nnn, where nnn is the three digit 8-bit character number from 0 to 255. For example, if output is to the screen, you can suppress all control characters except Tab, Carriage-Return, and Linefeed by *=*0:*8 *11:*12 *14:*31 *127/
To turn character replacement mode off, use a bare *=/. Repeat *=/ to turn replacement on again.

*/SEARCH/
Defines the search string, as used in the *(0...*) edit sequences discussed below. Or to locate lines in the input file for the *n command below. Or as a default search string in paging files.
A string matches the search string if its successive characters match the successive ones in SEARCH. For example, if SEARCH is John, then only "John" (without the quotes) in the searched text matches it.
However, if there are no capitals in SEARCH, the matchings are case insensitive. So if SEARCH is john, "John" in the searched text still matches it. However, if SEARCH is JOHN, "John" does not match it. (If needed, the command *s turns on case sensitivity and *i turns if off again.)
In general, at any stage, if the next character in SEARCH is CHR, then the next character in a matching string must normally be CHR too. So if SEARCH is John, then the first character in a matching string must be a "J", the second an "o", the third an "h", and the fourth an "n".
A first exception to that exists if CHR is followed by a ? in SEARCH. In that case all following characters CHR in the matching string, (zero or more), together match the CHR? in SEARCH. For example, if SEARCH is Joh?n, then "Jon", "John", "Johhn", "Johhhn", etcetera, in the searched text would all match it.
The ? of asku is similar to the * used in unix regular expressions. And indeed, the regular expression Joh*n is matched by exactly the same text strings as Joh?n in asku. But there may be a difference in other cases. In particular, in a regular expression, * really stands for zero or more, not for all like ? in asku. For example, assume the text to search contains "Johhhn". Then a SEARCH equal to Joh?h will not match any part of that text. The h? already matches all three h characters in "Johhhn", leaving no match for the final h in Joh?h. On the other hand, the unix regular expression Joh*h is ambiguous: the "Joh", "Johh", or "Johhh" parts of "Johhhn" are all acceptable matches. Which one is actually chosen depends on some additional concept, the amount of "greediness" applied to the regular expression. Probably "Johhh", if you do not modify the default greediness. If you flip the question mark over one place, the asku Johh? will match the "Johhh" part of "Johhhn", while regular expression Johh* is again ambiguous, either "Joh", "Johh", or "Johhh", with "Johhh" the likely result. Finally, adding an n, asku Johh?n and refular expression Johh*n are again fully equivalent. Both will match "John", "Johhn", etcetera, but not "Jon".
Returning to asku searches, another exception to simple character matching is if the next part of SEARCH is of the form [CHARS] where CHARS is a set of characters. In that case the next character in a matching string may be anyone of the characters in character set CHARS. For example, Jo[hn]n would be matched by either "John" or "Jonn".
An exception to that exception is for [CHARS]? in SEARCH. In that case, all following characters in a matching string that are in CHARS together match the [CHARS]? in SEARCH. So J[aho]?n would be matched by, for example, "Jon", "John", "Jan", "Joan" "Jahn", "Jaaahoooan", etcetera.
[-CHARS] is the same as [CHARS], except that characters match if they are not in CHARS. CHARS now list the excluded characters instead of the included ones. So [- *T*10*13] would be matched by any character that is not a Space, Tab (*T), or part of a DOS Newline character, (*10*13). And the matches to [- *T*10*13][- *T*10*13]? are the whitespace-terminated "words" in the searched text.
To specify a range of characters inside CHARS, use A:Z for uppercase letters, a:z for lowercase ones, 0:9 for digits, and *0:*31*127 for control characters.
One special case: inside SEARCH *W is taken to stand not for a simple space character, as usual, but for any of the standard "whitespace" characters Tab (*9), Linefeed (*10), Carriage-Return (*13), and Space (*32). To just specify a normal space, either type a normal space or use *32. The reason for the exception is that it allows you to specify [*9*10*13 ][*9*10*13 ]? "word" boundaries more concisely as *W*W?. Note also that *W as the upper boundary of a character range is still *32.
Within SEARCH, you must encode any normal star, *, as *X, as always, but also any forward slash, /, as *F. Otherwise it would be taken to be the final terminator behind SEARCH. You must also encode any normal character ? as *Q, and any normal [ by *O. Inside a CHARS substring, you will also need to encode any normal ] as *R and, if confusion is possible, - as *M and : as *C. (If a normal - is not at the start, or a normal : is not between characters, they are OK as is.) Arbitrary 8-bit characters can always be encoded as *nnn where nnn is the character number between 000 and 255. Leading zeros can be omitted if no confusion is possible. The control characters are *000 to *031 and *127. In particular *009 is Tab, *010 Unix Newline, and *13*010 Microsoft Windows DOS Newline.
Most commands are not allowed inside SEARCH. While *; is allowed, this is no different than entering *13*010 in DOS, or *010 in Unix. Using storage locations, *? and *@.../, is also allowed, at least outside [...] character sets. Whatever is in the storage location will be matched literally, (except for case if case sensitivity is off).
Note that the maximum length of SEARCH at the time of writing is only about 512 bytes, entire [...] character sets and *( and *) grouping symbols counting as one byte each. (Because asku stores SEARCH internally in a very inefficient way. You are welcome to rewrite it.)
Note: the *n command pads the line it searched to the left with a Linefeed, *010. That simplifies searching for a string at the start of the line. However, keep it in mind when searching for the Linefeed at the end of the line. Also note that not all files have a Newline at the end of their final nonempty line, although proper text files should.
Note: The following is an alternate description of SEARCH not involving exceptions on exceptions: A SEARCH always consists of a sequence of "units". Each "unit" consists of either a lone "subunit" or a "subunit" followed by a question mark. A "subunit" is either a single character CHR, a group [CHARS] where CHARS is a list of characters, or a group [-CHARS] where CHARS is a list of characters. To see whether a substring of a given text matches SEARCH, put a pointer at the start of the substring. Then process each unit in SEARCH in turn. For a unit without a question mark, the pointer must be moved one character over, and the passed character must be equal to CHR, one of the characters in CHARS, or a character not in CHARS, for the three types of subunits respectively. If this is not possible, the string does not match SEARCH. For a unit with a question mark, the pointer must be moved over all following characters equal to CHR, in CHARS, or not in CHARS, respectively. If no failure to match has occurred when all units in SEARCH have been processed, and the pointer is at the end of the substring, the substring matches.

*(0*=CHARREPS/*/SEARCH/REPLACE*)
Defines editing of the gathered data (see *g below). A bare *(0*) edit sequence would do nothing. However, you can follow the 0 by a *=.../ command to replace characters by strings.
You can also follow the 0 and any *=.../ command behind it by a (single) */SEARCH/REPLACE string. This will replace any string in the gather buffer that matches SEARCH by string REPLACE. The search string SEARCH is basically as defined above for the */SEARCH/ command. However, there is one addition, discussed below.
As a simple example, *(0*=r*T*W/*/John/J.*) will replace any "John" in the gathered data by "J." and also replace any Tab character by a blank.
The SEARCH string in */SEARCH/REPLACE can be an expression, rather than a simple literal string for more advanced editing, see */ above. For example:
   *(0*/*([A:Z]*)[a:z]?*([*W*T*10*13][*W*T*10*13]?[A:Z]*)/*(1*).*(2*)*)
Here [A:Z] stand for a capital letter, and [a:z]? stands for any amount of lowercase letters. Also, [*W*T*10*13] stands for a space, Tab, or Newline character. So, ignoring the *( and *) strings in SEARCH, the search here is for a capital, followed by any amount of lowercase characters, followed by one or more blanks, tabs, or newlines, followed by a capital. If the gathered data contains a string, say, "John Doe was always...", then the above SEARCH matches the "John D" part of it. But the above search would not match any part of "John was always...". The *(1*) in replacement REPLACE then takes the first *(...*) grouping in SEARCH, which is the first capital, "J" in the "John D" match. It puts a point behind it, producing "J." in the example. Then the *(2*) takes the second *(...*) grouping in SEARCH, being the spaces, tabs, newlines and the second capital, totalling " D" in the "John D" match. So "John Doe was always..." in the gathered data is replaced by "J. Doe was always...". But "John was always..." is not changed.
Note that up to nine *(...*) groups can be defined in SEARCH. Then the corresponding parts of the found string can be inserted as *(1*) to *(9*) in the replacement string REPLACE. (The *(...*) groupings in SEARCH cannot be nested inside each other; they must be consecutive.) In addition, *(0*) in REPLACE always gives the entire matched string, which would be "John D" in the example above.
The replacements can be even more sophisticated because the *(0*) through *(9*) REPLACE sequences can be edit sequences themselves. The digit in anyone can be followed by its own *=.../ command, and/or its own "*/SEARCH/REPLACE" sequence.
The *? and *@.../ commands may be used inside REPLACE to insert the user's input or kept strings. The *;, *r, *w, and *x commands may also be used.
See the *=.../ and */SEARCH/ commands above for the characters that need * encoding.
There are some subtle points with nontrivial SEARCH expressions that you need to recognize. An asku expression like [a:z*W]? always matches all following lowercase characters and blanks. This is unlike a unix regular expression like [a-z ]*, which may match only some, or even none of the following lowercase characters and blanks. So an asku SEARCH like begin[a:z*W]?end will never match anything. If there was, say, a string "begin and end." in the gathered data, then the [a:z*W]? will match the entire " and end" part, and the following e in SEARCH would fail to match the following "." of the string. In contrast, the unix regular expression begin[a-z ]*end would match the "begin and end" part of "begin and end.". However, I find that you can do similar things computationally quicker, and more reliably, in asku by using markers. The "begin and end" part can be found as
   *(0*/end/*1end*)*(0*=*1/*/begin[a:z*W]?*1end/REPLACE*)
This first puts character *1 in front of the "end" to prevent the [a:z*W]? to gobble it up. Then asku can search for "begin" followed by lower case letters and blanks followed by *1 followed by "end". After replacement of the found strings by REPLACE, the *=*1/ deletes character *1 again.
As far as reliability is concerned, assume the gather buffer contains a string
   "begin...end...begin...begin...end...end"
Then the regular expression begin[a-z. ]*end is poorly defined. For example, which "end" should match the first "begin"? Regular expressions use a concept like "greediness" to make some quite arbitrary choice here. An asku search is never ambiguous. Also, in the above string, you would probably want the first match to be from the first "begin" to the first "end". But a typical "greedy" regular expression would take the only match to be from the first "begin" to the final "end". The asku method above will give the desired first match from the first "begin" to the first "end". True, the asku procedure above will make the second match from the second "begin" to the second "end". You would probably want the second match to be the 'inner' one from the third "begin" to the second "end" instead. But you can easily modify the asku procedure to produce what you want, by using a second marker. For example:
   *(0*/begin/begin*1*)*(0*/end/*2end*)*(0*=*1:*2/*/begin*1[a:z*W]?*2end/REPLACE*)
The markers after the begin strings prevent matchings that cross a second "begin" between a "begin" and "end". So, as desired, the 'inner' match from the third "begin" to the second "end" will be found after the match from the first "begin" to the first "end". The *=*1:*2/ gets rid of any remaining stray markers.
Next you might want to have the second "begin" in the example match the third "end" in a second level of replacement of the 'outer' grouping. To do so, in the final edit sequence above, let the REPLACE get rid of the markers in the match. Then the above can be followed by another edit sequence to pick up the outer match from the second "begin" to the third "end". Move the final *=*1:*2/ command to this edit sequence to ensure that they are still there when the sequence does its string replacement.
Note that text files will not normally contain *1 (Ctrl+a) or *2 (Ctrl+b) control characters. If the file may have them, you can temporarily get them out of the way as follows:
   *(0*=*1:*8*15+16 *11:*12*15+16 *14:*15*15+16/*)...*(0*=*1:*8 *11:*12 *14/*)*(0*/*15*([-]*)/*(1*=*16:*31-16/*)*)
This makes control characters *1 through *8, *11, *12, and *14 available within ... as markers. The original control characters with those numbers are within ... encoded as *15 followed by a higher-numbered control character. Note that characters *9, Tab, *10, Linefeed, and *13, Carriage-Return, are a normal and important part of text documents, and you almost surely do not want to mess around with them. And character *15 is used to keep the original control characters safe, so it cannot be used as marker either. The above code makes sure to remove any remaining markers before restoring the original control characters. If *0, NUL, is not important to you, you can enable it as a marker by replacing =*1 above by =*0 twice. But in my experience, you rarely need more than a handful of markers. If you can do with *1 through *7 only, a shorter code than the above is
   *(0*=*1:*8*8+16/*)...*(0*=*1:*7/*)*(0*/*8*([-]*)/*(1*=*16:*31-16/*)*)
NUL can still be enabled as a marker as before. (Note also the elegant use of a nested edit sequence to restore the original control characters.)
Often, you may want to put markers at the start and end of the buffer. To put *1 at the start and *2 at the end, use
   *(0*/[-]?/*1*(0*)*2*)

This weird construct illustrates one final point. If the found string is nil, a match is ignored if it directly follows a previous match. So *(0*/[-]?/*1*(0*)*2*) above works correctly. It does not keep putting *1*2 pairs at the end of the buffer. And it does produce one *1, *2 pair even if the buffer is empty. Similarly, *(0*/[]?/_*) puts one underscore at the start of the buffer, between every two successive characters, and at the end of the buffer.
What a tangled web we weave... Or maybe there is a logic? Assume there is a separate "nothing" character, a single one being present between any two "real" characters, and that it is automatically absorbed in matches, being nothing after all. A second match will then of course fail because the nothing character is already absorbed. Does this start to make sense or should I reconsider this before having a beer?

*~iCNUM/ or *~oCNUM/
Reopens the input respectively output. If C is -, standard input respectively standard output is [re]opened. In that case NUM must be omitted. If C is ?, the file named in storage location number NUM is opened. NUM defaults to the last available storage location, as defined when asku was built (16 at the time of writing). If C is +, allowed for output only, output is sent to both standard output and the file named in storage location NUM. If C is g, allowed for input only, the current gather buffer is used as a virtual "standard input file". In that case, NUM must be omitted. And in this case, the gather buffer is immediately and completely copied into the input buffer, terminating in error if the input buffer size gets exceeded.
Note that essentially, *~i will reset all input-related variables to their original values, while *~o will leave output-related variables unchanged. The user beware. Also, *~i cannot be used in a loop, as this makes no sense.

*_NAME/
Returns the value of environment variable NAME, or nil if undefined.

*aITEMTYPE HELP DEFAULT MAX P8 SEP/
Asks the user for an item, just like one of the color, folder, menu, number, string, and value INPUT parameters would. One possible reason to use the *a.../ command is that it allows you to provide an input file to asku, through the freed-up INPUT parameter. The file might be a script that you want to edit with *(0...*) to include the user's input. Another reason to use the *aITEM.../ command could be to ask more than one item from the user.
ITEM should be the first letter of color, folder, menu, number, string, or value. After the letter, the space-separated parameters can be given. Unlike most command-line parameters, in *a.../ command all parameters must be star-encoded. At the very least, any * must be encoded as *X, including any leading * in HELP, and any space as *W. If any parameter has zero length, asku assumes that it keeps the same value it already has, from the command line or from a previous *a.../ command. If you want to remove a parameter, (i.e. give it zero length), specify it as *0 in the *a.../command. If the ... in *aITEM.../ end in trailing spaces, these may be omitted.

*b
Returns to the last *l label in TEMPLATE. At least it does so if the *l loop has not been terminated by a *c, *n, or *pp/ command inside it. Otherwise it only resets the current line number to whatever it was before the *l loop.

*c
If the next input line is out of range, causes the current loop to terminate, (see the *l and *b commands), or the entire TEMPLATE if none. Otherwise copies the next input line to the output. The current line number is correspondingly incremented by one.

*d
Outputs the current day of the month as two digits. Part of the *y, *m, *d, *h, *i, and *s date and time commands. See also the related *e command.

*eITEMDIGIT
Defines the extended date and time commands. ITEM is a character and DIGIT a digit. The specific meaning of the command depends on character ITEM.

*f
Like *c, but strips away any final newline sequence.

*g
If keeping is active (see *k), *g turns it off. Then *g starts gathering the following TEMPLATE output in the "gather buffer", instead of sending it to the normal output device. That allows you to do some processing on it before sending it to the output device with *o/. In particular, you can use *(0...*) edit commands on the gathered data. Gathering and keeping are initially both off and only one can be active at a time. To turn off gathering, use the *. command. A *o.../ or *k.../ command will turn it off too. Part of the *@, *., *g, *k, *o, and *z commands.
Note that the size of the gather buffer is limited, though significantly larger than the size of kept strings. At the time of writing 0.5 MB is available. Use asku.exe - - "*vG/*@/*;" to test.

*h
Outputs the current hour as two digits of a 24 hour clock. Part of the *y, *m, *d, *h, *i, and *s date and time commands. See also the related *e command.

*i
Outputs the current minute of the hour as two digits. Part of the *y, *m, *d, *h, *i, and *s date and time commands. See also the related *e command.

*jLINESPEC/
Jumps to a new location in the input file.
If LINESPEC is an integer without sign, it specifies a line number in the input file to go to. If that integer is followed by a ? then the line numbers are counted from the end of the input file. An integer preceded by a + or - sign specifies to go that many lines further into the input file, starting from the current position.
Note that if the location jumped to is out of range, it causes the current loop to terminate, see the *l command, or the entire TEMPLATE if none.

*kNUM/
If gathering or keeping is active, *k turns it off. Then *k starts keeping the following TEMPLATE output in storage location number NUM, instead of sending it to the normal output device. Such stored output can subsequently be output as *@NUM/. The default for NUM is the last available storage location, as defined when asku was built (16 at the time of writing). To turn off keeping the TEMPLATE output in location NUM, use the *. command. Another *k command, or a *g or *z one, will also turn it off. A *v command will if it changes the data at location NUM. Part of the *@, *., *g, *k, *o, and *z commands.
Note that stored strings cannot be longer than 8192 8-bit characters, at the time of writing. In addition, values of NUM are normally limited to be from 1 to 15, at the time of writing. To be precise, values from 0 to 16 are actually available, but 0 doubles as *? and the last location doubles as *@/, which are used by some asku TASKs.

*lLOW:HIGH/
The *lLOW:HIGH/ command provides a label in TEMPLATE to return to when looping. LOW and HIGH are the input line limits in the label's range. If they are unsigned, they indicate line numbers. If followed by a ?, the numbering is done from the end of the file. If LOW or HIGH start with a + or - sign, they are that many positions further than the current line number. The default LOW:HIGH is 0:0?, or for nested loops, whatever are the limits of the outer loop containing the current one. Explicit values of LOW and HIGH can change the range, but not beyond 0:0?.
As a simple example, a "*l/*c*b" TEMPLATE will print out all the lines in the file, one by one, as execution loops between *l/ and *b. And "*l/*j+1/*c*b" will print out only the even lines in the file.
The looping terminates when a *c, *j/, or *n command inside the loop exceeds the set limits or when the user quits in a *pp/ command inside the loop.
To understand the limits better, first note that according to asku an input file has the following general form:
<line 1><line 2>...<line EOF>
where EOF is an acronym for "End of File". Here all lines except <line EOF> terminate with a Newline (or more precisely Linefeed) character. And <line EOF> should be nil for a proper text file. However, asku allows it to consist of any number of characters not equal to Linefeed. Now if the input line number is 0, it means that the input file character pointer is at the end of nonexisting <line 0>. So it is also at the start of <line 1>, which means at the start of the file. If the line number is EOF, the input character pointer is at the end of <line EOF>, so at the end of the file. Since the value of number EOF is probably not known in advance, 0? can be used to indicate it. Hence the default loop limits are 0 and 0?. For a proper text file, 0? and 1? are the same.

*m
Outputs the current month of the year as two digits. Part of the *y, *m, *d, *h, *i, and *s date and time commands. See also the related *e command.

*n*(FOUND*)*(NOTFOUND*)
If the next input line is out of range, causes the current loop to terminate, see the *l/ command, or the entire TEMPLATE if none. Otherwise checks whether the next input line contains the search string, as set with the */ command. If it does, it leaves the input pointer where it is and executes FOUND. If the next line does not contain the search string, moves the input pointer to the end of the next line and executes NOTFOUND.
Note that *n commands may be nested for more sophisticated searching.

*oLINES/
Turns gathering off if it is on (see *g and *z). Then if LINES is zero or missing, outputs all the gathered data. If LINES is positive, outputs the first LINES lines of the gathered data. If lines is negative, all lines except the first -LINES ones are output. Part of the *@, *., *g, *k, *o, and *z commands.

*p.../
Provide a prompt for the user. Different forms if this command exist.

*qSTAT/ or *qeSTAT/
The first form quits with error status STAT, which must be a positive integer indicating failure or zero indicating success. The second form exits only with status STAT when a subsequent error occurs that asku looks for (like failure to open or read from the input file, or write to the output file). By default asku exits with error status 1 on such an error.

*rLOC/
Assumes that the stored string *@LOC/ is a valid file (or path) specification. Outputs its "root" to the output device. That means that the (apparent) final file extension and its preceding point is omitted. See also the related *v, *w, and *x commands. Use *vLOCF/ to clean up the file specification by removing redundant FOLDER\..\ and .\ (FOLDER/../ and ./ in Unix) strings.

*s
Outputs the current seconds of the minute as two digits. Part of the *y, *m, *d, *h, *i, and *s date and time commands. See also the related *e command.

*t.../
The *t command sets truncation handling.
*uCHRNO/
Outputs unicode character number CHRNO in UTF-8 encoding.
See the *tu8/ command above for important info on using UTF-8 in DOS.
Note that an *uCHRNO command without the trailing slash is also used inside *=.../ character encoding. There the *uCHRNO is terminated by a Space or the final slash of the *= command itself. See *=.../ for details.

*vLOC1LOC2TASK1TASK2TEST/*(YES*)*(NO*) or *vLOC1LOC2TASK1TASK2/
Warning: Starting with version 3 of asku, the default locations LOC1 and LOC2 have been swapped. Asku calls for versions 1 and 2 with *v commands will have to modified to work with later versions. Normally verifies a condition using test TEST and executes YES if the condition is true and NO if it is false. Argument LOC1 is the number of the first stored string to use in the test, and LOC2 that of the second stored string, if any. Arguments TASK1 and TASK2 are prior tasks to perform on the storage locations LOC1, respectively LOC2, (to give them values or to change their existing values). All arguments are optional. If the TEST is omitted, then so should *(YES*)*(NO*) be. In that case the *v command is just a tool to put various system data in storage locations. The default for LOC1 is the default storage location, being the number of the last available storage location as defined when asku was built (16 at the time of writing), and the default for LOC2 is 0. Note that LOC1 and LOC2 are restricted to a single digit, for *@0/ (equivalent to *?) to *@9/, or a hyphen, equivalent to the default, last available, storage location *@/. If *k keeping is active for LOC1 or LOC2, a nontrivial TASK on them will turn it off.
A TASK is indicated by a capital letter. The following TASKs are defined, many meaningful for unix but not Microsoft DOS. The defined TASK values are (leave away the quotes), The TEST is normally a single character. The defined TESTs are
*w
Outputs the current working folder.

*xLOC/
Assumes that the stored string *@LOC/ is a valid file (or path) specification. Outputs its (apparent) file extension, if any, with its preceding point, and with trailing whitespace stripped. See also the related *r, *v, and *w commands. Use *vLOCF/ to clean up the file specification by removing redundant FOLDER\..\ and .\ strings, (redundant FOLDER/../ and ./ strings in Unix).

*y
Outputs the last two digits of the current year. Part of the *y, *m, *d, *h, *i, and *s date and time commands. See also the related *e command.

*z
Deletes any existing gathered data and then turns gathering mode on. Part of the *@, *., *g, *k, *o, and *z commands.

Debugging TEMPLATE

Personally, I do not find asku templates hard to write. A lot easier than writing a dedicated program. Still, sometimes I do find myself mystified by what asku is doing, usually because of some stupid mistake. (The same as when I am writing a real program.) Here are some hints.

The biggest problem I have is not to write a working template, but the most elegant template.

TYPE

TYPE is the description of the item the user is asked to input. It is not star-encoded.

Some examples: "language to hyphenate in", "font size to use", "desired theme", "WordPerfect code page to use", etcetera.

TYPE may not contain any colons.

If TYPE starts with a capital or a space, it will be used as prompt as is (appending a colon and DEFAULT.) Otherwise a suitable prompt will be generated from it, by prepending "Enter the " or "Press the key of the " and appending something like " (or ? or q): DEFAULT".

Note that the maximum allowable length for TYPE will vary with INPUT and other parameters like DEFAULT. Watch in particular the "folder" task in DOS, just think of long strings like

   C:\Documents and Settings\Default User\Desktop\...
The total prompt line plus user input is only 79 8-bit characters.

DEFAULT

DEFAULT is the default for the item asked from the user. It is not star-encoded.

If DEFAULT is blank or missing, there is no default.

HELP

HELP is any item-specific help to give the user. It is star-encoded, so you must use *X if HELP contains a * character. (But why would it?)

If HELP is blank or missing, there is no help besides the generic one build into asku.

To have asku type a file named FILE on the screen, specify HELP as "?FILE". To have asku execute a command COMMAND by means of a system call, specify HELP as "*COMMAND". (Executing a command will agonize virus checkers).

MAX, P8, SEP

These parameters depend on what INPUT is and are described in more detail in that section. MAX and P8 are not star-encoded, but SEP is.

Download

Below is a pre-compiled asku.exe binary for various operating systems.

If your machine is not on the list above, you will need to download the asku source code and compile it yourself. This requires that you have either g77 or gfortran installed. Find the appropriate makefile, (modify it if needed), and run

   make -f make_g...
See the how_to_build.txt file for (a bit) more info. The source code also includes some files for testing purposes, following the Introduction and Examples section. (I run these examples on both Linux and MS before posting a new asku version.)

History

  1. Feb 3, 2014: Version beta 1.
  2. Apr 16, 2014: Version beta 2:
  3. Xxx xx, 201x: Version beta 3