'=========================================================================== ' Subject: READ COMMAND LINE ARGUMENTS Date: 09-12-99 (23:30) ' Author: Brian Marstella Code: QB, PDS ' Origin: brimars@yahoo.com Packet: DOS.ABC '=========================================================================== ' FileName: CMDDEMO2.BAS ' FileDate: 09/13/1999 ' FileVer: 1.1 - adds args$(0) program path ' (CMDDEMO.BAS was v1.0, original date 05/23/99) ' ' Version 1.1 returns the running program's path/filename on args$(0), which ' might be kinda handy if you'd like to create your INI files relative to ' the path of the program, rather than the directory the user is in. ' ' Purpose: Demonstrates how to read command-line parameters in and parse ' them neatly into an array. Reads the command-line case correctly and ' strip out command prefixes (- and / usually). Also retains quoted ' strings as 1 string, rather than splitting to multiple arguments. ' ' Status: Public Domain ' ' Language: QB45 (maybe others with some work; will not work with QBasic ' that comes with DOS unless you have someone's subroutines that will ' allow you to simulate DEF SEG and CALL INTERRUPT; may not work then) ' ' Will not work properly from the QB45 programming environment, due to ' the calls it uses. It will find the command line entered when starting ' QB, rather than any other parms. ' ' O/S: Works with DOS 3.3+, WIN95, WIN98. Also appears to work fine ' with DR-DOS 7.0+ and PC-DOS 7.0. I'm not sure if it will work with ' WINNT, OS/2, etc., due to the vastly different systems. ' ' Notes: Well, I actually wrote the subroutine for my own use. However, I ' know that I had a great deal of trouble figuring out how to use DEF SEG, ' call interrupt and some others, so hopefully I've documented this file ' well enough that it will help someone a little. ' ' Just so you know, this program is provided with absolutely no warranty ' of any kind. I don't guarantee that it will or won't do anything at all. ' ' If you would like to contact me for clarifications or whatever, you may ' e-mail me at brimars@yahoo.com. ' ' I'm sure that many of you who have programmed for years are disgusted ' by my formatting, extensive commenting, non-cryptic usages, child-like ' syntax, or whatever. Sorry. I wrote this for me and average people ' who might need to access the command-line. For C and assembly ' programmers, yes I know that there are many ways to do this much more ' cleanly, etc. Some people don't program in C and assembly, though, and ' might wish to have a way to do this in a somewhat clearer, more homey ' atmosphere than C (try to figure out how to get command line parms on ' your own in C; then try to use them). ' ' If you have constructive suggestions or find that I've missed an ' important method that a user can use to crash this, let me know. I'll ' try to make changes accordingly. ' ' Oh, also this program does not reference QB.BI; however, you must have ' QB.LIB available to compile. If QB.LIB is not present, you must expand it ' off of your distribution disks. Since this file may not have installed ' properly when you installed QB45, make sure you install it in your library ' directory. This library is absolutely required to compile files using ' the CALL INTERRUPT, CALL ABSOLUTE, etc. '************************************** ' Precompile instructions '************************************** '************************************** ' dynamic simply means that arrays can ' be redimensioned on the fly and ' allows ERASE to destroy arrays and ' free their memory. '************************************** '$DYNAMIC '************************************** ' Type Declarations '************************************** '************************************** ' type reg is required for call ' interrupt '************************************** TYPE reg ax AS INTEGER bx AS INTEGER cx AS INTEGER dx AS INTEGER bp AS INTEGER di AS INTEGER si AS INTEGER flags AS INTEGER END TYPE '************************************** ' Function Declarations '************************************** '************************************** ' Subroutine Declarations '************************************** DECLARE SUB getcmdline (argc%, arg$()) DECLARE SUB interrupt (intnum%, regsin AS reg, regsout AS reg) DECLARE SUB main (argc%, arg$()) '************************************** ' Constant Declarations '************************************** CONST DOSINT = &H21 CONST FALSE = 0 CONST TRUE = NOT FALSE '************************************** ' dimension the array to hold command ' line parameters '************************************** DIM args$(0) '************************************** ' Begin Main Program '************************************** ' the following statement should be your first call prior to the main ' program or any functions that attempt to reference commandline parms getcmdline argc%, args$() ' you may safely remove the sub main from the program; it's only function ' is to demonstrate the calls... main argc%, args$() '************************************** ' End Main Program '************************************** END REM $STATIC SUB getcmdline (argc%, arg$()) 'This subroutine returns the command line as entered (case of characters IS 'retained). Strips normal terminators (/ and -) and single & double quotes. 'Text enclosed in single and double quotes is kept as a continous string. '************************************** ' dimension working variables as needed '************************************** DIM regs AS reg DIM temparg$(0) '************************************** ' call the routine to get the PSP ' offset address (stores the command ' line parms among other things...) '************************************** regs.ax = &H6200 CALL interrupt(DOSINT, regs, regs) '************************************** ' now change the working memory segment ' to whatever value was returned in BX ' by the above call. '************************************** DEF SEG = regs.bx '************************************** ' first get the segment containing the ' environment block for the running ' program (which, happily, contains the ' running program's path in DOS 3.3+) ' ' the segment address is located at the ' word (2 bytes) at 2Ch and 2Dh '************************************** ppseg% = PEEK(&H2D) * 256 + PEEK(&H2C) '************************************** ' Offset 80h of this segment stores the ' entire length of the parameters ' entered (including the space between ' the program name and the parms!!!), ' so the command length is actually 1 ' less than what you are led to believe ' by good sense or experience on other ' systems '************************************** clen% = PEEK(&H80) - 1 cl$ = "" '************************************** ' if offset &h80 was greater than -1 ' then parms were entered; this section ' assembles them into a continuous ' string and notes that the argument ' counter (argc%) is at least 1 '************************************** IF clen% > -1 THEN FOR xloop% = 0 TO clen% cl$ = cl$ + CHR$(PEEK(&H80 + xloop% + 1)) NEXT xloop% argc% = 1 ELSE '************************************** ' if the value at offset 80h was 0 then ' no command line parms were entered ' and the argument counter is set to 0 '************************************** argc% = 0 END IF '************************************** ' now change back to the default memory ' segment; otherwise, you'll get tons ' of errors regarding corrupt string ' space and stuff. Besides, it's good ' practice '************************************** DEF SEG '************************************** ' begin assembling the arguments by ' first setting up the input array for ' 1 length, initialize it to null, and ' initialize a couple of other vars as ' needed for the subroutine '************************************** REDIM arg$(0 TO argc%) c$ = "" quotesopen% = FALSE '************************************** ' now start a loop to assemble the ' actual arguments entered into the ' input array area (arg$) (if any ' parameters were entered) '************************************** IF argc% > 0 THEN FOR xloop% = 1 TO clen% + 1 '************************************** ' set lc$ (last character) equal to the ' current character c$ then set c$ ' equal to the next character in the ' command line string (cl$) '************************************** lc$ = c$ c$ = MID$(cl$, xloop%, 1) '************************************** ' the select case determines what to do ' depending on what the next character ' c$ is '************************************** SELECT CASE c$ '************************************** ' if c$ is a space, then we first find ' out if quotes have started. if it's ' part of a quoted string, we simply ' add it to the current parameter and ' go about our business. however, if ' lc$ (last character) was not a space, ' it's not part of a quoted string, and ' it's not the first character in the ' parameter, then we assume that we ' have ended the current parameter. ' ' if the last character was a space, ' there's nothing that needs to be done ' ' if it's part of a quoted string, ' we've already taken care of it ' ' if it's the first character in the ' parms, then there's no sense making ' it a part of the current parm ' ' as an afterthought, we also check if ' the last character was a terminator. ' if so, this evaluation has no meaning '************************************** CASE " " IF quotesopen% = TRUE THEN arg$(argc%) = arg$(argc%) + c$ END IF IF (lc$ <> " " AND lc$ <> "-" AND lc$ <> "/" AND quotesopen% = FALSE AND xloop% > 1) THEN '************************************** ' if we made it this far, then we've ' reached the end of the current parm ' and need to prep for the next one ' first we redimension a temporary ' array to hold all the current parms, ' since we can't redimension the actual ' parm array (arg$) without destroying ' the parms we already collected '************************************** REDIM temparg$(0 TO argc%) '************************************** ' now we copy all the parms from the ' parm array arg$ to the temporary ' array temparg$ '************************************** FOR yloop% = 1 TO argc% temparg$(yloop%) = arg$(yloop%) NEXT yloop% '************************************** ' increment the argument counter (good ' a place as any to do it...) '************************************** argc% = argc% + 1 '************************************** ' now that all the parms are in a temp ' array, we'll redimension our actual ' array arg$ to the new number of args ' expected and then we'll copy the ' parms back using the same method as ' above '************************************** REDIM arg$(0 TO argc%) FOR yloop% = 1 TO argc% - 1 arg$(yloop%) = temparg$(yloop%) NEXT yloop% '************************************** ' next line initializes the current ' arg to NUL and the following lines ' dimension the temporary array back ' to zero to free memory '************************************** arg$(argc%) = "" ERASE temparg$ DIM temparg$(0) firstchar% = TRUE END IF '************************************** ' if the character in c$ is a single ' ' or double " quote, then we change the ' status of quotesopen%. this value is ' used simply to track whether we are ' in the middle of a text string (TRUE) ' or not (FALSE) '************************************** CASE CHR$(34), CHR$(39) IF quotesopen% = FALSE THEN quotesopen% = TRUE ELSE quotesopen% = FALSE END IF '************************************** ' the next case checks parm terminators ' which I simply strip unless they are ' part of a text string (quotesopen% is ' TRUE or the length of the parm is ' greater than zero). you can add more ' terminators or other characters to ' strip simply by adding them to the ' case statement separating each by a ' comma. make sure you also add them ' to the "space" evaluation case above '************************************** CASE "-", "/" IF quotesopen% = TRUE OR LEN(arg$(argc%)) > 0 THEN arg$(argc%) = arg$(argc%) + c$ END IF '************************************** ' if it's any other character, we just ' tack it onto the end of the command ' string and go on about our merry way '************************************** CASE ELSE arg$(argc%) = arg$(argc%) + c$ END SELECT NEXT xloop% '************************************** ' if the last argument is empty (which ' means that your user entered spaces ' or terminators at the end of the ' command line), then we kill it much ' the same way we added to the array. ' we also subtract 1 from the argument ' counter '************************************** IF LEN(arg$(argc%)) = 0 THEN argc% = argc% - 1 '************************************** ' if we no longer have any parms left ' then we can simply set the counter to ' zero, destroy the argument array '************************************** IF argc% = 0 THEN REDIM arg$(0) END IF '************************************** ' continue copying the array '************************************** REDIM temparg$(0 TO argc%) FOR xloop% = 1 TO argc% temparg$(xloop%) = arg$(xloop%) NEXT xloop% REDIM arg$(0 TO argc%) FOR xloop% = 1 TO argc% arg$(xloop%) = temparg$(xloop%) NEXT xloop% ERASE temparg$ REDIM temparg$(0) END IF END IF '************************************** ' now let's get the program's path ' info; 1st change the segment to the ' program's environment segment '************************************** DEF SEG = ppseg% '************************************** ' start looking through the data here ' until you locate a byte with 00h in ' it; this marks the end of the ' environment strings and the beginning ' of the program path (well, see the ' actual code comments...) '************************************** xcount% = 0 done% = 0 DO WHILE done% = 0 xcount% = xcount% + 1 '************************************** ' if the next values in the string are ' both &h00, that means that you are ' near the program path (4 bytes away) '************************************** IF (PEEK(xcount%) = &H0) AND (PEEK(xcount% + 1) = &H0) THEN '************************************** ' increment the offset count by 4 bytes '************************************** xcount% = xcount% + 4 nextchar% = PEEK(xcount%) arg$(0) = "" '************************************** ' get the program path; &h00 follows ' the path; we'll set the done% flag ' when we hit the &h00 '************************************** DO arg$(0) = arg$(0) + CHR$(nextchar%) xcount% = xcount% + 1 nextchar% = PEEK(xcount%) LOOP WHILE nextchar% <> &H0 done% = 1 END IF LOOP '************************************** ' finally, change back to the main ' data segment '************************************** DEF SEG '************************************** ' set c$ and lc$ = NULL to kill the ' space they're taking up '************************************** c$ = "" lc$ = "" END SUB SUB main (argc%, args$()) ' Sample subroutine that uses the arguments found on the command line. CLS PRINT "The program path is: "; args$(0) PRINT PRINT "Total Arguments: "; argc% PRINT IF argc% > 0 THEN FOR xloop% = 1 TO argc% PRINT TAB(5); "Argument #"; xloop%; " = "; args$(xloop%) NEXT xloop% END IF END SUB