/* ------------------------------------------------------------------ */ /* Directories, that is, files which contain other files, is a concept*/ /* unique to hierarchical file systems, something not universally */ /* implemented, most notably, in early versions of VM/CMS and MVS/TSO,*/ /* notable mainframe operating systems, as well as desktop computer */ /* systems like CP/M. Though this is probably of little interest to */ /* most, it is mentioned because one of REXX's strengths is its true */ /* cross-platform operability [that is, across widely varying OS, not */ /* *NIX variants and Win32 :) !]. The code examples shown here are not*/ /* cross-platform but strongly tied to the *NIX environment in keep- */ /* with the Perl Cookbook's *NIX orientation. */ /* */ /* Also, this section makes extensive use of both the 'rexxUtil' and */ /* 'rexxRe' libraries; scripts using this code will need to include */ /* the following at the start of the sript: */ /* */ /* call rxFuncAdd 'sysLoadFuncs', 'rexxUtil', 'sysLoadFuncs' */ /* call sysLoadFuncs */ /* call rxFuncAdd 'reLoadFuncs', 'rexxRE', 'reLoadFuncs' */ /* call reLoadFuncs */ /* */ /* and at the end: */ /* */ /* call reDropFuncs */ /* call sysDropFuncs */ /* ------------------------------------------------------------------ */ /* *NIX-specific approach: 'stat' utility Rename 'entry' to 'statinfo', a compound variable; leaf '.1' is a string containing: filename filetype size access modification change Other data may be obtained by altering value of 'statflds' */ filename = "/usr/bin/vi" statflds = "%n %F %s %x %y %z" ; cmd = "stat --format '" ||, statflds || "'" address SYSTEM cmd filename with OUTPUT STEM statinfo. if RC \= 0 then do say "Couldn't 'stat' " filename ":" RC ; exit RC end ; else do /* Parse and display data */ parse var statinfo.1 filename filetype filesize atime mtime ctime say "Filname: " filename say "Type of file:" filetype /* ... */ end /* ----------- */ /* Regina-only: also possible to utilise GCI facility to directly invoke the 'stat' C library function. This could be wrapped up in a native REXX function, as shown: UNIXstat : procedure expose (globals) filename = ARG(1) ; statinfo = NULL ... setup 'stat' with GCI ... ... invoke 'stat' ... ... parse and reformat 'stat'-returned data ... return statinfo GCI implementations of both, 'stat' and 'utime', appear in the Appendix */ filename = "/usr/bin" ; entry = UNIXstat(filename) if entry == NULL then do say "Couldn't 'stat' " filename ":" 1 ; exit 1 end ; else do /* Parse and display data */ parse var entry filename filetype filesize atime mtime ctime say "Filname: " filename say "Type of file:" filetype /* ... */ end /* ----------------------------- */ /* A more cross-platform [though with some Regina-specific options] approach using 'STREAM' BIF. Note more data is obtainable but not used in example, and only the modification time [not the status change / revision time] obtainable in this manner */ parse value STREAM(filename, 'C', 'FSTAT') with . . . . . . filesize parse value STREAM(filename, 'C', 'QUERY SIZE'), STREAM(filename, 'C', 'QUERY TIMESTAMP'), with filesize mtime /* ----------------------------- */ if STREAM(filename, 'C', 'OPEN READ') \= "READY:" then say "Error opening" filename /* ----------- */ /* It is not possible to determine whether a file / stream has been opened in 'text' or 'binary' mode, merely whether it contains data or not. If needed, the stream can be read and checks for the platform's 'line terminator' characters [usually CR, LF or CRLF] made */ if STREAM(filename, 'C', 'QUERY SIZE') == 0 then say filename "does not have data in it" /* ----------------------------- */ /* Hierarchical filesystem support cannot be assumed to exist on all platforms [though with the widespread adoption of *NIX or *NIX-based concepts on various platforms e.g. z/OS UNIX Sytem Services, Mac OSX, this is not as true as in the past]. For 'directory traversal' tasks it is common to see use made of the 'rexxUtil' library's, 'sysFileTree', routine */ dirname = "/usr/bin" if sysFileTree(dirname||"/", 'dirtree.', 'fso') \= 0 then do say "Couldn't open" dirname ":" 1 ; exit 1 end ; else do do i = 1 to dirtree.0 say "Inside" dirname "is something called" dirtree.i end end |
/* ------------------------------------------------------------------ */ /* Several library routines exist for querying file timestamps: */ /* */ /* * STREAM BIF */ /* * RexxUtil library's: sysGetFileDateTime [mtime, ctime only] */ /* sysSetFileDateTime [mtime only] */ /* */ /* but there is no support for modifying timestamps, where one has to */ /* resort to: */ /* */ /* * Invoking [via ADDRESS SYSTEM] a utility such as 'touch' */ /* * Binding to a C library function such as 'stat' or 'utime' via an */ /* interpreter-specific mechanism [such as Regina's GCI] */ /* */ /* The 'UNIXstat' and 'UNIXutime' routines used below [code included */ /* in the Appendix] are examples of the latter. */ /* ------------------------------------------------------------------ */ /* Update both access and modification time */ parse value UNIXstat(filename) with . . . READTIME WRITETIME . call UNIXutime NEWREADTIME, NEWWRITETIME, filename /* ----------------------------- */ SECONDS_PER_DAY = 60 * 60 * 24 parse value UNIXstat(file) with . . . atime mtime . parse value (atime - 7 * SECONDS_PER_DAY) (mtime - 7 * SECONDS_PER_DAY), with atime mtime if \UNIXutime(atime, mtime, file) then do say "couldn't backdate" file "by a week w/ utime" ; exit 1 end /* ----------------------------- */ /* Update access time only */ parse value UNIXstat(filename) with . . . . mtime . call UNIXutime TIME('T'), mtime, file /* ----------------------------- */ /* uvi - vi a file without changing its access times */ if ARG() < 1 then do ; say "usage: uvi filename" ; exit 1 ; end ; file = ARG(1) editor = VALUE("EDITOR",, SYSTEM) if editor == NULL then ; editor = "vi" address SYSTEM editor file if \UNIXutime(atime, mtime, file) then do say "couldn't restore" file "to orig times" ; exit 1 end exit 0 |
/* Cross platform approach using 'rexxUtil' library's, 'sysFileDelete', routine. Whilst a zero return code indicates success, a non-zero return code [actual value is platform-dependant] indicates the type of problem encountered e.g. not found, still in use, etc */ call sysFileDelete FILENAME if RESULT \= 0 then do say "Can't delete" FILENAME ":" RESULT ; exit RESULT end /* ----------------------------- */ FILENAMES = "f1 f2 f3 ..." ; allFilesDeleted = TRUE do while FILENAMES <> NULL parse var FILENAMES FILE FILENAMES call sysFileDelete FILE ; if RESULT then ; allFilesDeleted = FALSE end if \allFilesDeleted then do say "Couldn't delete all of" FILENAMES ":" 1 ; exit 1 end /* ----------------------------- */ filelist = "f1 f2 f3 ..." ; totfiles = WORDS(filelist) count = totfiles do while filelist <> NULL parse var filelist file filelist call sysFileDelete file ; if RESULT then ; count = count - 1 end if count \= totfiles then say "Could only delete" count "of" totfiles "files" |
/* Cross platform approach using 'rexxUtil' library's, 'sysCopyObject', routine. Whilst a zero return code indicates success, a non-zero return code [actual value is platform-dependant] indicates the type of problem encountered e.g. not found, still in use, etc */ call sysCopyObject oldfile, newfile if RESULT \= 0 then do say "Can't copy" oldfile "to" newfile ":" RESULT ; exit RESULT end /* ----------------------------- */ /* File copy effected by copying contents of an existing file to a newly-created file. Note: implict opening of file(s), and optional closing */ IN = 'oldfile' ; OUT = 'newfile' ; BUFSIZE = 256 do while CHARS(IN) > 0 char = CHARIN(IN,, BUFSIZE) ; if char <> NULL then ; call CHAROUT OUT, char end /* ----------------------------- */ /* Command-line utilities may easily be used for this task, but it is important to consider how any output [i.e. stdout, stderr] will be handled, and whether return codes are significant and whether they should be checked. */ /* *NIX */ dev = "/dev/null" ; cmd = "cp -pvd" oldfile newfile address SYSTEM cmd with OUTPUT STREAM dev ERROR STREAM dev if RC \= 0 then ; say "Error ..." /* OpenVMS */ dev = "NL:" ; cmd = "copy" oldfile newfile address SYSTEM cmd with OUTPUT STREAM dev ERROR STREAM dev if RC \= 1 then ; say "Error ..." /* Win32 [Return codes unreliable, so best parse output for command status] */ dev = "NUL:" ; cmd = "copy/v/b/y" oldfile newfile address SYSTEM cmd with OUTPUT STEM result. ERROR STREAM dev parse var result.1 numberCopied . if numberCopied \= 1 then ; say "Error ..." /* ----------------------------- */ /* Copies contents of source file to newly-created / truncated target file */ call sysCopyObject "datafile.dat", "datafile.bak" if RESULT \= 0 then do say "copy failed:" RESULT ; exit RESULT end /* ----------- */ /* Adjusts behaviour to either rename source file to target file [if they reside on the same device], or creates a new target file and copies contents of source file into it */ call sysMoveObject "datafile.new", "datafile.dat" if RESULT \= 0 then do say "move failed:" RESULT ; exit RESULT end |
/* ------------------------------------------------------------------ */ /* I'm not entirely sure what the code in this section is meant to do */ /* but I've proceeded on the assumption that, for a group of filenames*/ /* it is loading the corresponding device / inode pairs into a hash */ /* using those pairs as an alternate identifier for each file. */ /* ------------------------------------------------------------------ */ do_my_thing : procedure expose (globals) seen. filename = ARG(1) ; key = makeDeviceInodePair(filename) if SYMBOL('seen.key') \= 'VAR' then do /* Do something with 'filename' since not previously seen */ nop end return /* ----------------------------- */ files = "..." drop seen. keys. ; i = 0 do while files <> NULL parse var files file files key = makeDeviceInodePair(file) /* Either add a new entry or append to exisitng entry */ if SYMBOL('seen.key') \= 'VAR' then do seen.key = file ; i = i + 1 ; keys.i = key end ; else ; seen.key = seen.key file end keys.0 = i /* Sort keys */ call sysStemSort 'keys.', 'ascending' /* Traverse in sorted key order */ do i = 1 for keys.0 key = keys.i /* 'files' is a list of 1 or more filenames */ files = seen.key do while files <> NULL parse var files file files /* Do something with each filename ... */ end end exit 0 /* ----------- */ makeDeviceInodePair : procedure expose (globals) /* Could have used 'UNIXstat' custom function to do this */ cmd = "stat --format '%D %i'" address SYSTEM cmd ARG(1) with OUTPUT STEM devinode. if RC \= 0 then ; return NULL return devinode.1 |
/* ------------------------------------------------------------------ */ /* Directory traversal is typically performed using the 'rexxUtil' */ /* library's, 'sysFileTree', routine. Whilst it's full capabilities */ /* are not illustrated here, it is possible to generate lists of only */ /* files, only directories, or both, as well as the entire tree from */ /* the specified location; path can be a fully qualified name or a */ /* glob [e.g. *.*]. */ /* ------------------------------------------------------------------ */ dirname = "/tmp" /* Directory names must with the path separator character */ if sysFileTree(dirname||"/", 'files.', 'fo') \= 0 then say "Can't open directory" dirname /* Traverse file list */ do i = 1 to files.0 /* Do something with each file, accessed as: 'files.i' ... */ end /* ----------------------------- */ dirname = "/tmp" say "text files in" dirname "are:" if sysFileTree(dirname||"/", 'files.', 'fo') \= 0 then say "Can't open directory" dirname /* Traverse file list selecting only text files */ do i = 1 to files.0 if isTextFile(files.i) then say files.i end /* ----------- */ isTextFile : procedure expose (globals) /* No standard BIF for this task, so use 'file' utility [*NIX-only] */ cmd = "file -bN" address SYSTEM cmd ARG(1) with OUTPUT STEM filetype. if RC \= 0 then ; return FALSE return filetype.1 == "ASCII text" /* ----------------------------- */ /* '.' and '..' don't show up in 'sysFileTree' generated lists */ /* ----------------------------- */ plainFiles : procedure expose (globals) dirname = ARG(1) ; filelist = NULL /* Generated file list: - files only, via 'fo' option - automatically exclude '.' and '..' */ if sysFileTree(dirname||"/", 'files.', 'fo') \= 0 then do say "Can't open directory" dirname ; exit 1 end /* Sort stem */ call sysStemSort 'files.', 'ascending' /* Traverse sorted file list, and generate list of only 'regular' files */ do i = 1 to files.0 if isRegularFile(files.i) then ; filelist = filelist files.i end return STRIP(filelist) /* ----------- */ isRegularFile : procedure expose (globals) fstatinfo = STREAM(ARG(1), 'C', 'FSTAT') return WORD(fstatinfo, WORDS(fstatinfo)) == "RegularFile" |
/* ------------------------------------------------------------------ */ /* Filtering a list of files is performed via globbing [use made of */ /* the 'glob' system function, either directly, via a custom function */ /* which itself uses it ('sysFileTree'), or by invoking the shell (a */ /* simple trick is to issue an 'echo PATTERN' command), or by applying*/ /* regex patterns to a list of files. */ /* */ /* Whilst the Perl examples illustrate several variations of this bas-*/ /* ic approach, the present code will only make use of two custom fun-*/ /* ctions, 'glob' and 'grep', both of which may be found in the Appen-*/ /* dix. As the names imply, 'glob' uses 'glob' functionality via the */ /* 'sysFileTree' utility routine, and 'grep' utlises the regex routine*/ /* in the 'rexxRE' library. */ /* ------------------------------------------------------------------ */ list = glob("*.c") /* ----------- */ files = grep("\.c$", glob(path, 'NAME')) /* ----------------------------- */ files = grep("\.[cChH]$", glob(path, 'NAME')) /* ----------------------------- */ dirname = "/tmp" ; files = NULL if sysFileTree(dirname||"/", 'files.', 'fo') \= 0 then say "Can't open directory" dirname /* Traverse file list, and generate list of only 'text' files */ do i = 1 to files.0 if isTextFile(files.i) then ; files = files files.i end files = STRIP(files) /* ----------- */ isTextFile : procedure expose (globals) /* No standard BIF for this task, so use 'file' utility [*NIX-only] */ cmd = "file -bN" address SYSTEM cmd ARG(1) with OUTPUT STEM filetype. if RC \= 0 then ; return FALSE return filetype.1 == "ASCII text" /* ----------------------------- */ dirname = "/tmp" ; dirs = NULL /* Extract subdirectories only */ if sysFileTree(dirname||"/", 'dirs.', 'do') \= 0 then say "Can't open directory" dirname /* Traverse subdirectory building list of subdirectory names */ do i = 1 to dirs.0 dirs = dirs extractPathComponent(dirs.i, 'NAME') end /* Include only numerics in final list */ dirs = grep("^[[:digit:]].*$", STRIP(dirs)) |
/* ------------------------------------------------------------------ */ /* This section mainly illustrates various uses of the 'sysFileTree' */ /* 'sysFileTree' routine, the rough equivalent in functionality of */ /* Perl's 'File::Find' module. */ /* ------------------------------------------------------------------ */ dirlist = "..." do while dirlist <> NULL parse var dirlist dir dirlist /* 'processFiles' implemented in next section - applies 'file_proc' to each file */ call processFiles dir "file_proc" end /* ----------- */ file_proc : procedure expose (globals) file = ARG(1) /* ... do something to file ... */ return /* ----------------------------- */ dirname = "." ; if ARG(1, 'E') then ; dirname = ARG(1) if sysFileTree(dirname||"/", 'filetree.', 'dso') \= 0 then say "Can't open directory" dirname /* Traverse file tree ... */ do i = 1 to filetree.0 say filetree.i || "/" end /* ----------------------------- */ dirname = "." ; if ARG(1, 'E') then ; dirname = ARG(1) dirsize = 0 if sysFileTree(dirname||"/", 'filetree.', 'fso') \= 0 then say "Can't open directory" dirname /* Traverse file tree ... */ do i = 1 to filetree.0 dirsize = dirsize + getFileSize(filetree.i) end say dirname "contains" dirsize "bytes" /* ----------- */ getFileSize : procedure expose (globals) fstatinfo = STREAM(ARG(1), 'C', 'FSTAT') return WORD(fstatinfo, WORDS(fstatinfo) - 1) /* ----------------------------- */ /* fdirs - find all directories */ /* [1] - 'sysFileTree' reports only directories */ dirname = "." ; if ARG(1, 'E') then ; dirname = ARG(1) if sysFileTree(dirname||"/", 'filetree.', 'dso') \= 0 then say "Can't open directory" dirname /* Traverse file tree ... */ do i = 1 to filetree.0 say filetree.i end /* ----------- */ /* [2] - 'sysFileTree' reports both files and directories; filter directories out manually */ dirname = "." ; if ARG(1, 'E') then ; dirname = ARG(1) if sysFileTree(dirname||"/", 'filetree.', 'bso') \= 0 then say "Can't open directory" dirname /* Traverse file tree ... */ do i = 1 to filetree.0 if isDirectory(filetree.i) then ; say filetree.i end /* ----------- */ isDirectory : procedure expose (globals) fstatinfo = STREAM(ARG(1), 'C', 'FSTAT') return WORD(fstatinfo, WORDS(fstatinfo)) == "Directory" |
/* ------------------------------------------------------------------ */ /* This section utilises techniques earlier illustrated [use of the */ /* 'sysFileTree' routine, and custom functions 'glob' and 'grep']. Key*/ /* difference here is that directory traversal is packaged into two */ /* custom functions ['processDirectories' and 'processFiles'] and use */ /* of the 'interpret' instruction is made to apply functions to each */ /* directory / file [like a 'foreach' routine on a list or string]. */ /* ------------------------------------------------------------------ */ /* rmtree1 - remove whole directory trees like rm -r */ if ARG() < 1 then do ; say "usage: rmtree1 dir .." ; exit 1 ; end do i = 1 for ARG() call removeFileTree ARG(i) end exit 0 /* ----------- */ removeFileTree : procedure expose (globals) dirname = ARG(1) /* *NIX */ dev = "/dev/null" ; cmd = "rm -fr" dirname address SYSTEM cmd with OUTPUT STREAM dev ERROR STREAM dev return /* ----------------------------- */ /* rmtree2 - remove whole directory trees like rm -r */ if ARG() < 1 then do ; say "usage: rmtree2 dir .." ; exit 1 ; end do i = 1 for ARG() call removeFileTree ARG(i) end exit 0 /* ----------- */ removeFileTree : procedure expose (globals) dirname = ARG(1) call processDirectories dirname, "removeDir", "removeFile" call removeDir dirname return /* ----------- */ removeDir : ; call sysRMDir ARG(1) ; return removeFile : ; call sysFileDelete ARG(1) ; return /* ----------- */ processDirectories : procedure expose (globals) dirname = ARG(1) ; dirproc = ARG(2) ; fileproc = ARG(3) cmd = "call" dirproc "dir" call sysFileTree dirname||"/", 'dirtree.', 'do' if dirtree.0 > 0 then do do i = 1 to dirtree.0 call processDirectories dirtree.i, dirproc, fileproc dir = dirtree.i ; interpret cmd end end call processFiles dirname, fileproc return /* ----------- */ processFiles : procedure expose (globals) dirname = ARG(1) ; proc = ARG(2) ; cmd = "call" proc "file" call sysFileTree dirname||"/", 'files.', 'fo' do i = 1 to files.0 file = files.i ; interpret cmd end return |
NAMES = "f1 f2 f3 ..." do while NAMES <> NULL parse var NAMES file NAMES ; newname = " ... " call sysMoveObject file, newname if RESULT \= 0 then say "Couldn't rename" file "to" newname ":" RESULT end /* ----------------------------- */ /* Command-line utilities may easily be used for this task, but it is important to consider how any output [i.e. stdout, stderr] will be handled, and whether return codes are significant and whether they should be checked. */ /* *NIX */ dev = "/dev/null" ; cmd = "mv" oldfile newfile address SYSTEM cmd with OUTPUT STREAM dev ERROR STREAM dev if RC \= 0 then ; say "Error ..." /* OpenVMS */ dev = "NL:" ; cmd = "rename" oldfile newfile address SYSTEM cmd with OUTPUT STREAM dev ERROR STREAM dev if RC \= 1 then ; say "Error ..." /* Win32 [Return codes unreliable, so best parse output for command status] */ dev = "NUL:" ; cmd = "ren" oldfile newfile address SYSTEM cmd with OUTPUT STEM result. ERROR STREAM dev parse var result.1 numberCopied . if numberCopied \= 1 then ; say "Error ..." /* ----------------------------- */ /* Note: Implementation is barely functionally equivalent to Perl's [owing to Perl's superior regex facilities], and is not as flexible, in particular it: * Expects a list of files on the command-line * Requires the 'expr' to be a regex because it will be passed to the 'subst' routine * Requires that 'expr' be passed as two arguments: - 'from' -> regex - 'to' -> new name */ /* rename - Larry's filename fixer */ if ARG() < 1 then do ; say "usage: rename from to files" ; exit 1 ; end from = ARG(1) ; to = ARG(2) ; files = ARG() do i = 3 to files old = ARG(i) ; new = subst(old, from, to) if new \= old then ; call sysMoveObject old, new end exit 0 |
/* ------------------------------------------------------------------ */ /* This is no more than a parsing task, easily performed using PARSE */ /* in combination with BIF's such as POS and LASTPOS. A simple example*/ /* follows: */ /* */ /* PATHSEP = "\" ; path = "c:\d1\d2\d3\file.ext" */ /* */ /* drive = "-1" */ /* if POS(":", path) == 2 then do */ /* parse var path drive ":" name "." extension */ /* end ; else do */ /* parse var path name "." extension */ /* end */ /* */ /* basename = SUBSTR(name, LASTPOS(PATHSEP, name) + 1) */ /* subdir = LEFT(name, LASTPOS(PATHSEP, name)) */ /* */ /* say "[" || drive || "|" || name || "|" || extension || "|", */ /* || basename || "]" */ /* say subdir */ /* */ /* For convenience this functionality has been included as the: */ /* */ /* extractPathComponent */ /* extractPathComponents */ /* */ /* routines included in the Appendix. These routines currently only */ /* recognise *NIX and Win32 paths, so cannot be said to be cross- */ /* platform. It should not be too difficult to extend them to also */ /* recognise paths / filespecs on other platforms such as: */ /* */ /* VMS: NODE"user pass"::device:[dir.subdir]filename.type;ver */ /* MacOS: drv:dir:file */ /* MVS: dsnlvl1.dsnlvl2.dsnlvl3(member) */ /* dsnlvl1.dsnlvl2.dsnlvl3 */ /* CMS: fn fmode ftype */ /* ------------------------------------------------------------------ */ base = extractPathComponent(path, 'NAME') dir = extractPathComponent(path, 'SUB') parse value extractPathComponents(path, "NAME SUB EXT"), with base dir ext /* ----------- */ path = "/usr/lib/libc.a" file = extractPathComponent(path, 'NAME') dir = extractPathComponent(path, 'SUB') say "dir is" BL(dir) || ", file is" file /* ----------- */ path = "/usr/lib/libc.a" parse value extractPathComponents(path, "NAME SUB EXT"), with name dir ext say "dir is" BL(dir) || ", name is" name || ", extension is ." || ext /* ----------- */ path = "Hard%20Drive:System%20Folder:README.txt" parse var path drive ":" folder ":" name "." ext dir = drive || ":" || folder say "dir is" dir || ", name is" name || ", extension is ." || ext |
/* ------------------------------------------------------------------ */ /* Program: symirror */ /* ------------------------------------------------------------------ */ @@INCOMPLETE@@ @@INCOMPLETE@@ |
/* ------------------------------------------------------------------ */ /* Program: lst */ /* ------------------------------------------------------------------ */ @@INCOMPLETE@@ @@INCOMPLETE@@ |