home *** CD-ROM | disk | FTP | other *** search
- TITLE Concat
-
- ;Input:
- ; Read in multiple files (as named on the DOS cmd line).
- ; Wildcards and full pathing are supported.
- ;
- ;Output:
- ; To StdOut (e.g., via redirection at the DOS command line).
- ; (No ^Z is appended to the output.)
- ;
- ;End of File:
- ; Assumes the first ^Z in each file is EOF (and the rest is garbage),
- ; so will discard everything after that first ^Z.
- ;Notes:
- ; Makes no attempt to add terminating CR/LF to each file
- ; or any other such cleanup.
-
- ;Released to the public domain
- ;David Kirschbaum
- ;Toad Hall
- ;kirsch@braggvax.ARPA
-
- CR equ 0DH
- LF equ 0AH
- CTRLZ equ 1AH ;Ctrl-Z
-
- STDOUT equ 1 ;DOS StdOut
- STDERR equ 2 ;DOS StdErr (screen)
-
- DTA_TYPE struc
- reserved db 21 DUP(?) ;Used by DOS for find next
- attrib db ? ;file attribute
- time dw ? ;file time stamp
- date dw ? ;file date stamp
- filesize dw ?,? ;file size in bytes (long integer)
- filename db 13 DUP(?) ;AsciiZ filename string
- DTA_TYPE ends
-
- CSEG segment public 'CODE'
- assume CS:CSEG,DS:CSEG
-
- org 80H
- dta label DTA_TYPE
- nchar db ? ;for Args
- params db ?
-
- ; org fcb_dta + 26 ;dta filesize
- ;filesize dw ?,?
- ;
- ; org dta + 30 ;DTA.filename
- ;dtaname db ? ;for wildcard expansion
-
- org 100H ;yep, a .COM file
-
- Concat proc near
- jmp Start ;skip over data
-
- usagemsg db 'CONCAT - Concatenate text files to StdOut',CR,LF
- db 'Usage: CONCAT [d:\]file1.txt file2.txt >output.txt',CR,LF
- db 'Input: Wildcards and full pathing permitted.',CR,LF
- db 'Output: DOS Standard Output (redirectable).',CR,LF
- db 'David Kirschbaum, Toad Hall, v1.0'
- crlf db CR,LF
- USAGEMSGLEN equ $ - usagemsg
- CRLFLEN equ $ - crlf
-
- notfoundmsg db ' not found',CR,LF
- NOTFOUNDLEN equ $ - notfoundmsg ;msg length
-
- openerrmsg db ' File open error',CR,LF
- OPENERRLEN equ $ - openerrmsg
-
- readerrmsg db ' File read error',CR,LF
- READERRLEN equ $ - readerrmsg
-
- writerrmsg db ' File write error',CR,LF
- WRITERRLEN equ $ - writerrmsg
-
- zerofilemsg db ' Empty file, skipped',CR,LF
- ZEROFILELEN equ $ - zerofilemsg
-
- handle dw 0 ;input file handle
-
- Concat endp
-
-
- ;Returns position of char in string
- ;DI -> string start or end
- ;AL = char to seek
- ;Returns psn in CX
- ;It'll work in reverse also if you enter with DI -> string end
- ;and STD instead of CLD
-
- Find_Char proc near
- mov cx,0FFFFH ;max scan
- repne scasb
- not cx ;CX=char psn
- ret
- Find_Char endp
-
-
- ;Display an AsciiZ string to StdErr
- ;Enters with SI -> AsciiZ string
- ;Destroys AX,DI
- ;Returns CX = string length, DX, SI -> AsciiZ string
-
- Pr_AsciiZ proc near
- xor al,al ;find terminating 0
- mov di,si ;from string start
- cld ;go forward
- call Find_Char ;CX = psn of 0, SI unchanged
- dec cx ;- 1 for actual length
- mov dx,si ;string start
- mov bx,STDERR ;output to StdErr
- mov ah,40H ;write to file/device
- int 21H
- ret
-
- Pr_AsciiZ endp
-
-
- ;Separate a drive:\path from an arg filename.
- ;SI -> arg (an AsciiZ string)
- ;Pointer pathend -> beyond last char in path (filename[0] if none)
-
- Split_Path proc near
-
- mov di,si ;from string start
- xor al,al ;find AsciiZ arg end
- cld ;go forward
- call Find_Char ;CX=psn of 0, DI -> beyond 0
- dec di ;DI -> AsciiZ 0
- dec di ;DI -> last char in filename
-
- mov al,'\' ;scan for subdirectories
- std ;backwards towards start
- repne scasb ;CX has filename length
- cld ;insure forward again
- or cx,cx ;any path?
- jnz Got_Path ;yep
-
- ;No '\',how about ':'?
-
- xor cx,cx ;assume no path, no move
- mov di,si ;string start
- cmp byte ptr 1[di],':' ;divider should be after drive char
- jnz No_Path ;nope, DI -> filename's first char
-
- ;In either case, DI should be pointing to the char
- ;BEFORE the path or drive separator.
-
- Got_Path:
- add di,2 ;bump beyond '\' or ':'
- mov cx,di ;path\filename end
- sub cx,si ;- path end = bytes to move
- No_Path:
- mov di,offset path ;move into path
- jcxz No_Path_Move
- rep movsb ;copy args path into buffer
- No_Path_Move:
- mov word ptr pathptr,di ;pathptr -> char beyond args path
- ;where we'll move the filename
- Split_Path endp
-
-
-
- ;Move our expanded filename after our arg path.
-
- Add_Path proc near
- mov si,offset dta.filename ;expanded filename
- mov di,si ;Find_Char wants it in DI
- xor al,al ;get AsciiZ name length
- cld ;forward
- call Find_Char ;CX = char psn, SI unchanged
- mov di,word ptr pathptr ;-> arg path + 1 (or start)
- rep movsb ;move in the name
- ;(include the AsciiZ 0)
- mov si,offset path ;-> path start
- ret
- Add_Path endp
-
-
- ;Displays expanded filename
- ;Copies the wildcard file to StdOut
- ;Enter with SI -> expanded filename
- ;On return:
- ; handle = file handle (or 0 if never opened)
- ; CF set = error
- ; DX=CR/LF or error msg (if error)
- ; CX = msg length
-
- Copy_File proc near
- mov handle,0 ;insure file handle is cleared
-
- call Add_Path ;add arg path to expanded filename
- ;(we could be doing a Find Next)
- ;returns SI, DX -> path:\name
- call Pr_AsciiZ ;display AsciiZ name
- ;DX -> AsciiZ path:\name
-
- mov ax,3D00H ;open for read only
- int 21H
- jb Open_Err ;failed
-
- mov handle,ax ;save the handle
-
- mov si,offset dta.filesize ;DTA filesize long integer
- lodsw ;get filesize.lo
- or ax,[si] ;test filesize.hi
- jz Zero_File ;empty file, forget it
-
- ReadLup:
- mov dx,offset readbuff ;file read buffer
- mov cx,READBUFFSIZE ;bytes to read
- mov bx,handle ;file handle
- mov ah,3FH ;read from file/device
- int 21H
- jb Read_Err ;read failed
-
- mov cx,ax ;bytes read
- jcxz File_Eof1 ;nothing read, it's EOF
-
- mov bx,ax ;remember bytes read
- mov di,dx ;DI -> read buffer
- mov al,CTRLZ ;scan for terminating Ctrl Z
- repne scasb
- jz File_EOF ;it's logical EOF
-
- mov cx,bx ;restore nr bytes read
- mov bx,STDOUT ;output to StdOut
- mov ah,40H ;write to file/device
- int 21H
- jnb ReadLup ;wrote ok, keep going
-
- Write_Err:
- mov dx,offset writerrmsg ;' File write error'
- mov cx,WRITERRLEN
- ret ;CF set
-
- Zero_File:
- mov dx,offset zerofilemsg ;' Zero file size .. skipped'
- mov cx,ZEROFILELEN
- ret ;CF clear
-
- Read_Err:
- mov dx,offset readerrmsg ;' File read error'
- mov cx,READERRLEN
- ret ;CF set
-
- File_Eof:
- inc cx ;adjust for that Ctrl Z
- sub bx,cx ; bytes read - Ctrl Z psn
- mov cx,bx ;= bytes to write
- mov bx,STDOUT ;output to StdOut
- mov ah,40H ;write to file/device
- int 21H
- jb Write_Err ;final write failed
-
- File_Eof1:
- mov dx,offset crlf ;new line after filename
- mov cx,CRLFLEN
- ret ;CF should be clear
-
- Open_Err:
- mov dx,offset openerrmsg ;'File open error'
- mov cx,OPENERRLEN
- ret ;CF set
-
- Copy_File endp
-
-
- ;Do a wildcard copy.
- ;SI -> AsciiZ filename
-
- OutPut proc near
-
- mov dx,si ;DX = AsciiZ filename ptr
- xor cx,cx ;no special attributes
- mov ah,4EH ;find first
- int 21H
- or ax,ax ;find anything?
- jz Found_First ;yep
-
- call Pr_AsciiZ ;display filename (still in SI)
- mov dx,offset notfoundmsg ;' not found'
- mov cx,NOTFOUNDLEN ;msg length
- stc ;CF set to exit
- jmp short Not_Found ;display error msg, return
-
- ;Remember, a Find First or Find Next will give us an expanded filename
- ;(in the DTA) but will NOT give us a path!
- ;We have to process the arg (SI still points to it) and split off
- ;the path from the filename.
-
- Found_First:
- call Split_Path ;separate arg path from arg
- ;arg filename (for Find First only).
- ;fall thru to...
- Find_Next: ;our wildcard loop
- call Copy_File ;open, copy the file
-
- ;On return:
- ; handle = file handle (or 0 if never opened)
- ; CF set = error
- ; DX=CR/LF or error msg (if error)
- ; CX = msg length
-
- Not_Found:
- pushf ;save copy flags
- mov bx,STDERR ;Std Err handle
- mov ah,40H ;write to file/device
- int 21H
-
- mov bx,handle ;restore input file handle
- or bx,bx ;ever opened?
- jz No_Handle ;nope
- mov ah,3EH ;close input file
- int 21H
- No_Handle:
-
- popf ;restore copy flags
- jb OutPutX ;some sort of error
-
- ;Now for the Find Next
- mov ah,4FH ;continue file search
- int 21H
- cmp al,18 ;no more files to be found?
- jnz Find_Next ;nope, found one, continue
- OutPutX:
- ret ;all done with this file name
-
-
- OutPut endp
-
-
- Start proc near
- call _Args ;process cmdline into argv array
- or ax,ax ;any args?
- jnz Concat_Lup ;yep, concat them
-
- mov dx,offset usagemsg ;'Usage:'
- mov cx,USAGEMSGLEN
- mov bx,STDERR
- mov ah,40H ;write to file/device
- int 21H
- jmp short Concat_Term ;terminate
-
- Concat_Lup:
- mov si,word ptr argv[2] ;snarf an arg
- or si,si ;null arg?
- jz Concat_Done ;yep
- call OutPut ;copy file to StdOut
- call _Shift ;shift the args down
- jmp Concat_Lup
-
- Concat_Done:
-
- Concat_Term:
- mov ax,4C00H
- int 21H
-
- Start endp
-
-
- ;---- args.asm ----------------------------------------------------------
- ;
- ; Args parses the command line into a unix-style parameter array.
- ; Argc and Argv are just as in C; argc = # of params on cmd line,
- ; argv is an array of pointers to strings (null terminated, of course).
- ; Because MS-DOS doesn't pass us the name used to invoke the program,
- ; argv[0] always points to a null string.
- ; _Shift updates argc.
- ;
- ; Do not call NEW before calling _Args.
- ;
- ;--------------------------------------------------------------------------
-
- ; Maximum number of parameters
- MAXPARMS equ 32
-
-
- Args2 proc near
-
- jmp Start
-
- Args2 endp
-
- _Args proc near
-
- ; initialize
-
- cld ;insure fwd
-
- mov di,offset argv ;first arg
- mov cx,MAXPARMS ;max parms allowed
- xor ax,ax
- rep stosw ;point all args at null
-
- mov bx, 2 ; argc = 0 (will sub 2 from bx at end)
-
- mov si,offset nchar ;cmdline char count
- lodsb ;snarf, bump SI ptr
- mov cl,al ;into CX for counter
- xor ch,ch
- jcxz A_Done ; no arg chars -> we're done.
-
- mov di,offset argbuff ;big arg buffer
-
- ; Move arguments out of default DTA.
-
- push cx
- push di
- mov dx,2000H + 'a' ;handy constant
- A_UpperLup:
- lodsb ;snarf PSP cmdline char
- cmp al,CR ;CR?
- jz Stuff0 ;yep
- cmp al,dh ;space?
- jnz A_NotSpace ;nope, more checking
- Stuff0:
- xor al,al ;replace with a 0
- jmp short A_Stuff
-
- A_NotSpace:
- cmp al,dl ;'a' ;lower case?
- jb A_Stuff ;nope
- sub al,dh ;20H ;uppercase it
- A_Stuff:
- stosb ;stuff arg char into argbuff
- loop A_UpperLup
- xor al,al
- stosb ;insure terminating 0
- pop si
- pop cx
-
- ; Big loop- find arguments...
-
- A_ParmL:
- lodsb ; al = [si++]
- or al,al ;former space separator?
- loopnz A_ParmL ;keep gobbling until we find one
- jcxz A_Done ; found no unblank chars -> we're done.
-
- mov word ptr argv[bx],si ; save pointer to this string
-
- add bx,2 ; argc++;
-
- cmp bx, MAXPARMS*2
- jae A_Done ;done
- or cx,cx ;stop any CX wraparound to 0FFFFH!
- loopnz A_ParmL ;decr CX, reloop
-
- A_Done:
- ; All done finding parms; now share argc with caller.
-
- mov ax,bx
- sub ax, 2 ;adjust for argv[0]
- ;(traditionally program name)
-
- shr ax, 1
- mov word ptr argc,ax ;save argc
- ret
-
- _Args endp
-
- ;---- _Shift: --------------------------------------------
- ; Shifts %2 to %1, %3 to %2, etc. Leaves %0 alone.
- ; Works by shuffling argv[*].
-
- _Shift proc near
-
- cld
- mov si, offset argv[4]
- mov di, offset argv[2]
- mov cx,word ptr argc ;move this many args
- rep movsw
- dec word ptr argc ;decr arg counter
- ret
-
- _Shift endp
-
-
- ;---- parameter count, array ----------------
- ; Pointers to up to MAXPARMS parameter strings are held here.
-
- even ;keep things neat
-
- argc equ $ ;really just a byte
- ;same as dw ?
- argv equ argc + 2 ;dynamic argv array of pointers
- ;same as dw MAXPARMS DUP(?)
- argbuff equ argv + (MAXPARMS ShL 1) ;dynamic args buffer
- ;same as db 128 DUP(?)
-
- readbuff equ argbuff + 128 ;dynamic read buffer
- ;same as db 4096 DUP(?)
- READBUFFSIZE equ 4096 ;4Kb for now
-
- pathptr equ readbuff + 4096 ;pointer to path start
- ;same as dw ?
- path equ pathptr + 2 ;path:\filename buffer
- ;same as db 128 dup(?)
-
- CSEG ENDS
- end Concat