Version beta 3
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:
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.
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.batInside mktrans.bat,
asku.exe color "?color.bat" "*:*F/set col=*@/*;" "color" "FFFFFF" "?help.txt" call color.batcould 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.batUnix 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.
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.
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 - -
Print a string without starting a new line at the end in DOS:
asku.exe - - "The current folder is: *" & cdThe 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.
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.)
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.
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.
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.
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.
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.
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.
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*)*;"
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.
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.batFor 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.batThe *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 437Don'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.batwhere 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 errorThe 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 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:
set "L2HTOP=%%^&dir" if not "%L2HTOP%" == "%%^&dir" goto enverrorNote 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.
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.)
~®غאָ %#&^;.!~@#$%^&()_+`-={}[];',.®©Đύьغ₧ﺕאָ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
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.)
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.
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.
C:\try\!~@#$&()_+`-={}[];',.0123456789abcdefghijcontaining 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.)
call "%%L2HTOP%%\bin\showhelp.bat" %%1 "%%L2HTOP%%\help\intro.htm"Without the doubled quotes, a % or ^ in L2HTOP or parameter 1 would cause failure.
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/*;"
call "@hlptmp_.bat"Or else.
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 #.
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:
"$L2HTOP/bin/prog1" "par=`'$L2HTOP/bin/prog2' '$L2HTOP/data/data.dat'`"will fail.
% | \ | " | ` | $ |
%% | \\\\ | \\" | \\` | \\$ |
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.
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:
To simplify entering special characters, and to enter more complicated TEMPLATE expressions, "star-encoding" must be used for various parameters. The rules are:
*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 |
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 is the source of input: the user, a file, or standard input. It is not star-encoded.
The various possibilities for INPUT are:
FIELDN SEP ... SEP FIELD3 SEP FIELD2 SEP FIELD1where 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.
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 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 - - " *"
In addition to simple characters, you can use commands in TEMPLATE. A list of them now follows.
More details below:
*(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.
*(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.
"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.
*(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.)
*(0*/[-]?/*1*(0*)*2*)
asku.exe - - "*eH2:*i:*s *=aAM pPM/*eN1"Reset a and p character replacement afterwards with *=aa pp/ if you plan to do more output!
asku.exe - - "*ed1 *z*m*(0*/01/janvier*)*(0*/02/février*)...*o/, *ey0*;"will print the month in French if the terminal supports the used encoding of the accented characters.
<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.
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 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 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 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).
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.
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.)