REXX Tips & Tricks:General hints for OS/2 & DOS commands

From EDM2
Jump to: navigation, search

This section contains some useful hints for using OS/2 & DOS commands (with REXX programs and without REXX programs).

CTRL-Break & OS/2 commands

There's a bug in CMD.EXE in the handling of CTRL-BREAK while executing a system command from within a REXX program: Instead of passing this signal to the REXX program, the CMD.EXE terminates the REXX program. This is also true for .EXE files called from a REXX program if they pass the CTRL-Break to the CMD.EXE. (Source: page How do I ... on the home page of Quercus Systems (see Internet - Web Pages))

To avoid this error, call a new copy of the CMD.EXE to execute system commands. For example:

 "cmd /c" "dir \ | rxqueue"
                       /* the variable RC contains the return code   */
                       /* of the dir command                         */

(see also #The CMD command and #The functions LINEIN() and PULL())

Using meta chars

To use one of the OS/2 meta chars (e.g. |, <, >, ...) as normal, literal chars in OS/2 commands you can precede the meta char with a caret ^. This char, known as an "escape character", will keep the meta char from being interpreted by the shell.

Thus, whereas the meta char ">" normally will redirect output from a program or other source, the two characters "^>" will be seen by the system as ">" with no special meaning. This is one way in which you could execute the following command without producing an error:

 echo The objectID of the Connection folder is ^<WP_CONNECTIONSFOLDER^>.

To use a literal caret character you must use ^^.

The other method to use these chars as literal chars is to surround the whole string with double quotes ".

To prevent the CMD.EXE from interpreting the percent char % you can also use two of them: %%. That's also necessary if you want to use the percent char in a string enclosed in double quotes.

To use filenames beginning with a dash (-) without having the system interpret the filenames as command parameters, use either the absolute path or a relative path in your command.

Example:

 attrib -r .\-test.log
 attrib -r c:\temp\-test.log
Note
Users of alternate command shells such as 4OS2 should check to see what char their command interpreter is using as the escape char (CMD.EXE uses "^") before using the caret symbol. Some command interpreters allow the user to change the character used. This is usually controlled by a setting in an INI file or environment variable.

STDIN, STDOUT, STDERR, ...

STDOUT / STDERR

Some OS/2 commands (for example DEL) write their output to STDERR if STDOUT is redirected to a file, pipe or device.

Redirect STDOUT and STDERR

If you want to redirect STDOUT and STDERR of an OS/2 command, you should always redirect STDERR first. If, instead, the reverse of this is used, some programs (including CMD.EXE) sometimes write their error messages to the display regardless of the redirection of STDERR.

To redirect STDOUT and STDERR into the same FILE use the following construct:

"attrib a:test.cmd 2>>errFile 1>>&2"

To redirect STDOUT and STDERR into the same PIPE use the following construct:

"attrib a:test.cmd 2>&1 | pipeit.cmd"

Please note that other command line interpreter, such as 4OS2, for instance, may use another syntax for the redirection of STDOUT and/or STDERR.

Note
See also #STDOUT / STDERR, #How to check if STDIN, STDOUT or STDERR are redirected to a file see #General input line enhancer for an example use of STDERR and STDOUT.

Using Pipes

The number of PIPEs (|) in one OS/2 command is limited to n, where n depends on the length of the current value of the PATH variable. To get around this limitation, group together parts of your command in parentheses.

Example: Instead of

 a | b | c | d | e | f

use

(a | b | c) | (d | e | f)

(where a, b, c, d, e and f are OS/2 commands or programs). The parts in parentheses are executed by separate CMD processes and no CMD process must create more than 2 PIPEs.

Source
c't 1/1996

(see also Pipes & Errorlevel)

Using "Named Pipes"

It should be possible to use Named Pipes as input or output device for OS/2 commands, for example:

                   /* create a Named pipe called "\PIPE\MYPIPE"     */
                   /* with on of the functions from                 */
                   /* RxNPipe - Named Pipes for REXX                */
                   /* RexxIPC - interprocess communication for REXX */
                   /* RXU - powerful DLL for REXX                   */

        /* now call for example the cmd to use that pipe for STDIN  */
"start /c cmd <\PIPE\MYPIPE"

        /* now you could write the commands for the cmd to execute  */
        /* to the pipe \PIPE\MYPIPE                                 */

But until now I did not manage to implement a working solution using Named Pipes for this purpose.

How to check if STDIN, STDOUT or STDERR are redirected to a file

To check, if STDIN, STDOUT or STDERR are redirected to a file use the following code:

/* This code works in Object-Oriented REXX only! */

  STDIN_type = stream( 'STDIN', 'c', 'QUERY STREAMTYPE' )
  if STDIN_type = "PERSISTENT" then
    say "STDIN is redirected to a file."
  else
    say "STDIN is the screen or a pipe or NUL."

  STDOUT_type = stream( 'STDOUT', 'c', 'QUERY STREAMTYPE' )
  if stdout_type = "PERSISTENT" then
    say "STDOUT is redirected to a file."
  else
    say "STDOUT is the screen or a pipe or NUL."

  STDERR_type = stream( 'STDERR', 'c', 'QUERY STREAMTYPE' )
  if stderr_type = "PERSISTENT" then
    say "STDERR is redirected to a file NUL."
  else
    say "STDERR is the screen or a pipe."

Pipes & Errorlevel

If you're using pipes in REXX programs, remember that the special REXX variable RC always contains the return code of the last program in the command containing the pipes.

Example: /* */

 address cmd 'dir | sort | find "<DIR>" '
                   /* The next statement will always print the       */
                   /* return code of the find command as the         */
                   /* value of RC                                    */
 say 'RC=' || rc

To read the return codes of the programs in the middle of the pipe as well, you can use a REXX wrapper.

Example: /* ------------------------------------------------------------------ */ /* PushRC - simple wrapper program to save the return code of an */ /* OS/2 command or program in the current REXX queue */ /* */ /* Usage: pushRC program parameter */ /* */ /* History */ /* 01.09.1996 /bs v1.00 */ /* - initial release */ /* */ /* */ /* */ /* */

 parse arg progName progParameter
 "cmd /c" progName progParameter
                   /* progname (or something similar) is necessary   */
                   /* because OS/2 is a multi-tasking                */
                   /* operating system                               */
 push progName "RC="rc

Usage example:

/*                                                                    */
  address cmd "pushrc dir | pushrc sort | pushrc find ""<DIR>"" "

  do while queued() <> 0
    say lineIn( "QUEUE:" )
  end /* do while queued() <> 0 */

(see also #Using PIPEs)

Hints for some OS/2 commands

This section contains hints for some OS/2 commands. Some of these hints are not well known, but they may help you get more out of the commands or help users to employ the commands more productively.

Note that the information about internal commands is only valid for the CMD.EXE. It might not be correct for other command line interpreters, such as 4OS2.

The ARCRECOV command

This command is used to restore the desktop from a previously created desktop archive. The syntax is:

ARCRECOV x

where x can be 1, 2, 3 or X.

The reason for these numbers is that WARP's desktop archiving facility stores up to a maximum of 3 archives, plus its original files (X).

The current version of ARCRECOV does not display any usage instructions.

Source: The OS/2 WARP Survival Guide

The BACKUP command

The BACKUP command in WARP 4 supports the undocumented switch /O. When this switch is used, BACKUP will process open files.

(WARP 4 only!)

Source: The OS/2 WARP Survival Guide, APAR JR09859.

The BOOT command

The BOOT command supports the undocumented switch /N. Using this switch, the BOOT command prepares the hard disk for the requested OS but it will not reboot the system.

Source: The OS/2 WARP Survival Guide

The CACHE command

Use the undocumented switch

/WRITECACHE:size_in_bytes

of the CACHE command to specify the maximum size of writes to be cached. The default value for this parameter is 65,536; the maximum is also 65,536.

Source: The OS/2 WARP Survival Guide

Use the undocumented switch /Dstg:on to get some more information and switches for CACHE.EXE. Use CACHE /Dstg:on /? to get a syntax help for the undocumented switches.

(WARP 4 only! but the switches /DirtyMax:n and /Writecache:n also work in WARP)

Source: Team OS/2 WEB pages

The CALL command

Use the OS/2 command CALL with the undocumented switch /Q to suppress all messages from the called CMD. (Do not confuse the OS/2 command CALL with the REXX statement CALL!)

Source: The OS/2 WARP Survival Guide

The CHKDSK command

There must be at least one blank between the drive specifier and the slash indicating further options if using CHKDSK for an HPFS formatted drive.

Example:

CHKDSK C:/F

will work for FAT formatted drives, but not for HPFS formatted drives.

CHKDSK C: /F

will work for both, FAT formatted and HPFS formatted drives.

The CMD command

The /S parameter does not disable the signal handler for CMD.EXE as documented.

Source: APAR PJ18883

The value for the parameters /P and /K must be enclosed in two leading and two trailing double quotes if the name of the program to execute contains blanks or special chars.

Example

cmd /c ""MY TEST.CMD""

Source: Brian Havard (see EMail Addresses)

The COPY command

The switches /Y and /-Y for the COPY command are only mentioned in the Online Help but not yet implemented.

(WARP 4 only!)

Source: APAR JR09625

Copying open or locked files

To copy locked DLLs or EXEs you can unlock them with the unlock program from the LXlite package.

To copy open files you can use the program ROBOSVUT.COM from the RoboSave package.

And last, if you don't have one of the tools mentioned above handy: Use the TYPE command to copy an open text file:

type openFile >targetFile

(see Detach a program for an usage example; see also The BACKUP command)

Caution: Use this method only for ASCII files because TYPE interprets some ASCII codes!

Copying empty files

You cannot copy an empty file (zero byte length) with the COPY command. To copy an empty file you may use the TYPE command:

type emptyFile >targetFile

Note that the XCOPY command can copy empty files. The COPY command of 4OS2 can also copy empty files. And, of course, you can also write a REXX program to copy empty files.

The DETACH command

The DETACH command writes the message with the process ID of the detached program to STDERR.

To catch this ID, you can redirect STDERR of the DETACH command into a file (see Redirect STDOUT and STDERR). Than you can use any program that can read open files to process this file.

Unfortunately REXX programs cannot read open files. Therefore you have to use a little trick to get the PID in a REXX program (see Detach a program)

Note: The STDERR of the DETACH command is used by the DETACH command and by the detached process. Therefore, if you redirect STDERR of DETACH into a file, this file is open until the detached process ends. Note also that you cannot pipe the output of the DETACH command to another program.

All programs started from a detached session use the standard input, output and error handles of the detached session. You cannot use the START command to start another session from a detached session. You can start some but not all PM programs from a detached session. For example PMREXX.EXE works but VIEW.EXE doesn't. To start a usable, interactive CMD session from a detached session you might use the command:

 "pmrexx rexxtry cmd"

Or, a more general solution, use the WPS interface: SysSetObjectData. Example:

 rc = SysSetObjectData( '<WP_OS2WIN>', 'OPEN=DEFAULT;' )

The DIR command

Use the undocumented switch /V of the DIR command to produce formatted output.

(WARP 4 only!)

Source: Team OS/2 WEB pages

The FDISK command

Use the undocumented switch /newmbr to force FDISK to write a new master boot record to the harddisk. This is equal to the switch /mbr of the DOS FDISK command. The switch is documented in the Online Reference of WARP 4.

If FDISK refuses to do something you want it to do (e.g. create or delete a partition), use FDISK in non-interactive mode with parameters. In most cases this helps.

Please be aware that when FDISK is run in non-interactive mode the position (i.e., order) of parameters given on the command line is significant.

Note: Do not rely on the return code of FDISK.

The EXTPROC command

You can use the EXTPROC command to instruct the CMD.EXE to automatically call your REXX program for your data files.

To provide a datafile with this capability, you must give it the extension .CMD and use EXTPROC as the first line in the datafile. The EXTPROC statement in the datafile will call the REXX program you specify following EXTPROC. (see examples below or download RxLBox for a working example). In addition, you will need to set up the specified REXX program to ignore the line of the datafile that starts with EXTPROC.

Example: Let's say we have a datafile called MYDATA1.CMD. The contents of the file may look like:

EXTPROC myprog1
datarecord 1
datarecord 2
datarecord 3

The REXX program MYPROG1.CMD could be launched automatically from MYDATA1. It might look like:

Example:

/* MYPROG1.CMD                                                      */
/* - sample REXX program for processing datafiles using EXTPROC     */
/*                                                                  */
/* This program assumes that the only parameter of the EXTPROC      */
/* statement is the name of the program to call. The name of the    */
/* datafile is automatically added by the CMD.EXE.                  */
/* You can use the fully qualified name of your CMD in the EXTPROC  */
/* statement. Or you can use only the name of your CMD (without     */
/* the path) if it's available via the environment variable PATH.   */
/* Example:                                                         */
/*                                                                  */
/*   EXTPROC MYPROG1.CMD                                            */
/*                                                                  */
/* or                                                               */
/*                                                                  */
/*   EXTPROC C:\test\MYPROG1.CMD                                    */
/*                                                                  */
/* This programs works only if the datafile is in the current       */
/* directory when it is run (see below)                             */
/*                                                                  */
  parse arg dataFile

  say 'The name of the datafile is:'
  say '  "' || datafile || '"'

                    /* now read the data file                       */
  fileStem.0 = 0
  i = 0
  do while lines( dataFile ) <> 0
                    /* ignore the EXTPROC line                      */
    curLine = lineIn( dataFile )
    if translate( word( curLine, 1 ) ) = 'EXTPROC' & i = 0 then
      iterate
    i = i+1
    fileStem.i = curLine
  end /* do while lines( dataFile ) <> 0 */
  fileStem.0 = i

                    /* do something ...                             */
  say fileStem.0 || ' lines read:'
  do i = 1 to fileStem.0
    say 'Line ' || i || ' is "' || fileStem.i || '"'
  end /* do i = 1 to fileStem.0 */

exit

Be sure your datafile is structured as in the above example, with the EXTPROC statement on the first line. (Note that the EXTPROC statement must be the first text in the file or it will not work. Note also that the @Echo off statement, commonly included at the beginning of batch files, cannot precede the EXTPROC statement.)

Now you can simply call the datafile to call your REXX program to process it via:

 MYDATA1

You can also add static parameters to the EXTPROC statement and/or use variable parameters when calling the data file.

The only disadvantage (you may call it a bug) of the EXTPROC feature is that the CMD.EXE always passes the name of the datafile without the path to your REXX program. Therefore either the directory with your datafile must be the current directory when calling the data file or you should use the workaround in the code below.

The workaround uses the fact that the CMD.EXE handles CMD files with EXTPROC statements like any other CMD file. This includes the processing of environment variables (for example %COMSPEC%) and parameters (for example %1, %2, etc.). Therefore we simply add the placeholder for the name of the CMD program, %0, as parameter for the EXTPROC statement. The %0 is always replaced with the name of the CMD as entered by the user.

An example datafile using this feature might look like:

EXTPROC myprog2 %0 /PARM staticParameter1 staticParameter2
datarecord 1
datarecord 2
datarecord 3

(The comments below, incorporated into MYPROG2.CMD, explain the use of the EXTPROC line shown in the MYDATA2.CMD example above and describe each parameter)

The REXX program MYPROG2.CMD to process this datafile might look like:

/* MYPROG2.CMD                                                      */
/* - sample REXX program for processing datafiles using EXTPROC     */
/*                                                                  */
/* The format of the EXTPROC statement for the datafile             */
/* invoking this program is:                                        */
/*                                                                  */
/*   EXTPROC MYPROG2.CMD %0 /PARM {staticParameters}                */
/*                                                                  */
/* MYPROG2.CMD is the name of the CMD to process the datafile.      */
/*                                                                  */
/* The string %0 is replaced by the name of the datafile as entered */
/* by the user. This is necessary, because MYPROG2.CMD needs it to  */
/* detect the directory with the datafile if it is not the default  */
/* directory.                                                       */
/*                                                                  */
/* The string /PARM is necessary because without it (or something   */
/* similar) MYPROG2.CMD cannot detect the begin of the static       */
/* parameters if the name of the datafile contains blanks.          */
/*                                                                  */
/* {staticParameters} are parameters for the CMD which you can      */
/* hardcode in the datafile. The number of static parameters is     */
/* not limited.                                                     */
/*                                                                  */
/* Assuming your datafile is called MYDATA2.CMD you can call it     */
/* with                                                             */
/*                                                                  */
/*    MYDATA2                                                       */
/*                                                                  */
/* or, if you want to use variable parameter                        */
/*                                                                  */
/*    MYDATA2 varParameters                                         */
/*                                                                  */
/* The number of variable parameters is not limited.                */
/*                                                                  */
/* If MYDATA2.CMD is not in the current directory you can call it   */
/* with an relative path like                                       */
/*                                                                  */
/*    ..\MYDATA2                                                    */
/*                                                                  */
/* or also with an absolute path like                               */
/*                                                                  */
/*    c:\test\MYDATA2                                               */
/*                                                                  */
/* In both cases you can add additional variable parameters if      */
/* you like.                                                        */
/*                                                                  */

  parse arg thisParameter

  say 'This is MYPROG.CMD called with the parameters'
  say '  "' || thisParameter || '"'

                    /* now split the parameter into filename and    */
                    /* real parameter                               */
                    /*                                              */
                    /* Note that we use the first parameter (that   */
                    /* is the %0 from the EXTPROC statement) as     */
                    /* name of the datafile and not the filename    */
                    /* added by the CMD.EXE. This is necessary      */
                    /* because the name added by the CMD.EXE never  */
                    /* contains the path of the file.               */
                    /*                                              */
  parse var thisParameter dataFile '/PARM' thisParameter

  dataFile = strip( strip( dataFile ), 'B', '"' )
  if translate( right( dataFile, 4 ) ) <> '.CMD' then
    dataFile = dataFile || '.CMD'

  dataFileName = filespec( 'N', dataFile )
  if translate( right( dataFileName, 4 ) ) <> '.CMD' then
    dataFileName = dataFileName || '.CMD'

                    /* now remove the filename added by the CMD.EXE */

                    /* in Object REXX you can use:                  */
/*  parse caseless var thisParameter tParm1 (dataFilename) tParm2   */
/*  thisParameter = strip( tParm1 tParm2 )                          */


                    /* in Classic REXX you must use:                */
  dataFileName = translate( datafilename )
  i = pos( dataFileName, translate( thisParameter ) )
  j = length( dataFileName )
  thisParameter = strip( substr( thisParameter,1, i-1 ) || ,
                         substr( thisParameter,i+j ) )

  say 'The parameter without the name of the datafile are:'
  say '  "' || thisParameter || '"'

  say 'The name of the datafile is:'
  say '  "' || datafile || '"'

                    /* now read the data file                       */
  fileStem.0 = 0
  i = 0
  do while lines( dataFile ) <> 0
                    /* ignore the EXTPROC line                      */
    curLine = lineIn( dataFile )
    if translate( word( curLine, 1 ) ) = 'EXTPROC' & i = 0 then
      iterate
    i = i+1
    fileStem.i = curLine
  end /* do while lines( dataFile ) <> 0 */
  fileStem.0 = i

                    /* do something ...                             */
  say fileStem.0 || ' lines read:'
  do i = 1 to fileStem.0
    say 'Line ' || i || ' is "' || fileStem.i || '"'
  end /* do i = 1 to fileStem.0 */

exit

You can call MYDATA2 with one of the following statements to see how it works (assuming you've copied the file into the directory C:\TEST, C: is your OS/2 boot drive and you've copied MYPROG2.CMD to a directory available through the environment variable PATH):

  MYDATA2

  MYDATA2 varP1 varP2 varP3

  c:\test\MYDATA2

  c:\test\MYDATA2 varP1 varP2 varP3

  c:\os2\dll\..\..\test\MYDATA2

  c:\os2\dll\..\..\test\MYDATA2 varP1 varP2 varP3

The FORMAT command

If you want to redirect the input for the FORMAT command, you should use one of the following techniques:

 "type format.dat | format C:"

or

 " program | format C:"

format.dat must be an ASCII file containing the user input for the FORMAT program; program can be any OS/2 program that writes the necessary user input for the FORMAT program to STDOUT (for example a REXX program). You can't use the ECHO command to pipe the input for the FORMAT command.

Note: see also Navigate another program

OS/2 2.11 users may use the undocumented switch /NOF to force a quick format. This is equal to the /Q switch in the OS/2 WARP format program.

Source: The OS/2 WARP Survival Guide

The HELP command

The file that executes the HELP command in OS/2 is a batch file. Therefore, calling the HELP command from within a REXX program generates the error message SYS1803. To avoid this error you must use either

'CMD /C HELP' parameter

or

'HELPMSG ' parameter

(HELPMSG is the program called from HELP.CMD).

The SET command

It is tricky, if not impossible, to use the character = in environment variables used with the SET command. For example the command

SET test=myVar=myValue

leads to the OS/2 error SYS1003. To get around this limitation, surround the value with double quotes:

SET test="myVar=myValue"

Or you can set the environment variable in the CONFIG.SYS. SET statements in the file CONFIG.SYS don't have this limitation (except that you cannot use the equal char in the name of an environment variable).

But be aware that you cannot use nested environment variables in the CONFIG.SYS. Example:

REM *** Example SET statements for
REM     the CONFIG.SYS
REM
SET varA=ValueA

REM *** The next statement does not
REM     work as expected if used
REM     in the CONFIG.SYS!
REM
SET varB=%varA% ValueB

REM *** The next statement will
REM     only work in the CONFIG.SYS
REM
REM ---name----  ----Value----
SET configEnvVar=myVar=myValue

By the way: The characters |, < and > don't have a special meaning if used in commands in the CONFIG.SYS.

In any case, in REXX programs you should use the REXX function VALUE to get or set the value of environment variables. The function VALUE does not have this limitation.

(see also #Using meta chars)

The SHUTDOWN command / The SETBOOT command

Use the command SHUTDOWN to perform a system shutdown from the command line or a batch or REXX program. SHUTDOWN needs no parameter. You can also use the command

SETBOOT /IBD:x

where x is the drive you want to boot from next or a non-existing drive if you do not want to reboot the PC.

The difference between them:

Using SHUTDOWN OS/2 pops up a warning dialog for every open DOS or OS/2 session, whereas SETBOOT closes all DOS, Windows and OS/2 applications without any prompting.

Beginning with Fixpack #6 for WARP 4 there is another program to perform a system shutdown from the command line: TSHUTDWN.EXE.

TSHUTDWN.EXE does a normal system shutdown without pop up dialogs for OS/2 or DOS sessions. Note that you must unpack TSHUTDWN.EXE manually using the command

unpack TSHUTDWN.EX_

if you don't have a machine with WorkSpace on-Demand. The file TSHUTDWN.EX_ is in the fixpack directory \OS2.3.

According to a note from Armin Schwarz (see EMail Addresses) there are some restrictions using TSHUTDWN.EXE:

"The readme of Warp4 FP8 says to use /N to eliminate any prompts when shutting down. I found no difference between using /N or not using that option.

TSHUTDWN does shut down OS/2 window, DOS and seamless WIN-OS2 window sessions without prompting. BUT IT WILL PROMPT TO CLOSE A FULLSCREEN OS/2 SESSION.

TSHUTDWN must be used on a Warp 4 FP8 system. IT WILL NOT SHUTDOWN WITHOUT PROMPTING ON ANY OTHER WARP 3 OR 4 SYSTEMS. In other words, TSHUTDWN works just like SHUTDOWN on Warp 3 and Warp 4 (pre FP6)."

The SYSINSTX command

Use the command SYSINSTX.COM (normally in the directory \OS2\INSTALL\BOOTDISK or on the installation disk) to make an OS/2 drive or diskette bootable. Note that for using this command on HPFS drives the DLL UHPFS.DLL must be in a directory in the LIBPATH.

Source: The OS/2 WARP Survival Guide

The XCOPY command

In OS/2 WARP 4 you may get the error SYS1186 while trying to copy files with odd characters in their names (e.g. "ý1þ%Eþ.AXþ") using XCOPY. To resolve this problem, you must change the codepage to 437. To do this either issue the command CHCP 437 for a temporary change, or change the codepage line in the file CONFIG.SYS to CODEPAGE=437,850 and reboot the system to make the change permanent.

Please note that I cannot reproduce this error on my system with WARP 4 and Fixpack #1.

According to the OS/2 online help, XCOPY should return a return code of 2 if it cannot access one or more source or target files. In reality (OS/2 WARP 3 with Fixpack #17) XCOPY returns 0 in this case.

(Note: I've tried this again with WARP 4 Fixpack #5 - and at least in this environment XCOPY does return a return code of 2 in case of an error.)

To get around this bug, you can use a REXX program (see below) to call XCOPY, redirect its output to the REXX queue and check the contents of the REXX queue afterwards to get the results of the XCOPY command (see also The COPY command).

Warning: XCOPY is a version dependent program! Always use the XCOPY.EXE from the current active OS/2 version!!!

    
    /* sample REXX cmd to get around some of the bugs of XCOPY            */

                        /* init global variables                          */
      sourceFiles = "*.*"
      targetFiles = "test1\*.*"

                        /* flush the current REXX queue                   */
      do i = 1 to queued(); parse pull; end;

      say "Now calling xcopy to copy " || ,
           sourceFiles || " to " || targetFiles || " ..."

                        /* execute XCOPY and redirect STDOUT and STDERR   */
                        /* into the REXX queue (because the filenames go  */
                        /* to STDOUT but the error messages go to STDERR) */
      "@xcopy" sourceFiles TargetFiles "2>>&1" "| rxqueue"

      say " ... done. The RC of XCOPY is " || rc || "."

      say
      say "Now checking the result ... "
      say

                        /* get no. of lines in the REXX queue             */
      qCount = queued()

                        /* number of files copied                         */
      filesCopied = 0

                        /* no. of files not copied due to source file     */
                        /* access errors                                  */
      sourceFileErrors = 0

                        /* no. of files not copied due to target file     */
                        /* access errors                                  */
      targetFileErrors = 0

                        /* no. of files not copied due to unknown errors  */
      unknownErrors = 0

                        /* error marker for the loop                      */
      errorFound = 0

      do i = 1 to qCount
        parse upper pull curLine

        curLine = strip( curLine )
        if curLine = "" then
          iterate

        select

          when errorFound = "SourceError" then
          do
            sourceFileErrors = sourceFileErrors + 1
            say " Error: XCOPY could not access the source file "
            say "        " || curLine
            errorFound = ""
          end /* when */

          when errorFound = "TargetError" then
          do
            targetFileErrors = targetFileErrors + 1
            say " Error: XCOPY could not access the target file "
            say "        " || curLine
            errorFound = ""
          end /* when */

          when word( errorFound,1 ) = "UnknownError" then
          do
            unknownErrors = unknownErrors + 1

                        /* check if the next line is the name of a file   */
            if stream( curLine, "c", "QUERY EXISTS" ) <> "" then
            do
              say " Error: XCOPY retrieved the error " || ,
                  word( errorFound,2 ) || " accessing the file "
              say "        " || curLine
            end /* if stream( ... */
            else
              say " Error: XCOPY retrieved the error " || ,
                  word( errorFound,2 )

            errorFound = ""
          end /* when */

                        /* SYS1186 - source file access error            */
          when left( curLine,7 ) = "SYS1186" then
            errorFound = "SourceError"

                        /* SYS1187 - target file access error            */
          when left( curLine,7 ) = "SYS1187" then
            errorFound = "TargetError"

          when left( curLine,3 ) = "SYS" & substr( curLine,8,1 ) = ":" then
            errorFound = "UnknownError" substr( curLine,1,7 )

          otherwise
                        /* ignore status output of XCOPY                  */
            if stream( curLine, 'c', 'QUERY EXISTS' ) <> '' then
              filesCopied = filesCopied + 1

        end /* select */

      end /* do i = 1 to qCount */

      say
      say "XCOPY copied " || filesCopied || " file(s)."

      filesNotCopied = sourceFileErrors + targetFileErrors + unknownErrors
      if filesNotCopied <> 0 then
      do
        if filesNotCopied <> 1 then
          say filesNotCopied || " file(s) were not copied due to access errors."
        else
          say filesNotCopied || " file was not copied due to access errors."

        say " " || sourceFileErrors  || ,
            " file(s) because of source file access errors, "

        say " " || targetFileErrors  || ,
            " file(s) because of target file access errors "

        say " and " || unknownErrors || " file(s) because of unknown errors."
      end /* if filesNotCopied <> 0 then */
      else
      do
        say " No errors found."
      end /* else */
    exit 0

Hints for some DOS commands

This section contains hints and undocumented information about some DOS commands under OS/2. Note that the information provided here is. as far as I know, only true for the DOS included in OS/2 WARP.

Further note that information about the external family mode commands (these are external commands usable in DOS and OS/2 sessions) are included in the section Hints for some OS/2 commands.

If you're looking for more DOS secrets you may take a look at the website http://www.mdgx.com/secrets.htm.

Please be aware, that I do not know if the tips mentioned there are working in the DOS box of OS/2 also.

Thanks to Michael Lueck (see EMail Addresses) for this information.

The ASSIGN command

The DOS command ASSIGN included in OS/2 supports the nearly undocumented switch /S to display the current assignment status. ("nearly undocumented" because ASSIGN /? shows this switch, but you can't find anything about it in the online help)

Source: The OS/2 WARP Survival Guide

The DATE command

Use the undocumented switch /N of the DOS command DATE to display the date on the screen without prompting to enter a new date.

Source: The OS/2 WARP Survival Guide

The DIR command

Use the switch /R of the DIR command to get the long name of a file in a DOS session (works only for files and directories that have the EA .LONGNAME set)

Example output of DIR with the /R switch:

BMT_MICR        134173  29.05.96 11.28 BMT Micro Shareware Catalog

The EXIT_VDM command

Use the DOS command EXIT_VDM to close a DOS session independent from the number of called nested command processors (EXIT only closes the last called command processor).

Source: The OS/2 WARP Survival Guide

The ISWINDOW command

Use the return code of the DOS command ISWINDOW to distinguish between windowed and fullscreen DOS session (0 = fullscreen, 1 = window). Example:

  @ECHO OFF
  REM *** sample DOS batch to distinguish
  REM     between window and fullscreen

  REM --- call the util program
  iswindow
  if errorlevel 1 goto IsWin

:IsFS
  SET sessionType=FS
  ECHO. Running in a DOS Fullscreen session
  GOTO NEXT

:IsWin
  SET sessionType=WIN
  ECHO. Running in a DOS window session
:NEXT

The MODE command

The MODE called with the parameter co#,# to set the window height and width command accepts any combination of lines and columns as long as the formula "lines x columns" does not exceed 8192.

Example:

mode co70,112

mode co160,51

You do not have to use the co, e.g.

mode co160,51

is the same as

mode 160,51

Source: Found in a message in a public news group

The READLINE command

Use the nearly undocumented DOS command READLINE to read a line from the keyboard and place it in the batch parameter variables. Use the parameter /V for READLINE to convert the input into upper case. ("nearly undocumented" because READLINE /? works but you can't find anything about it in the online help)

Note: READLINE reads from the keyboard - not from STDIN.

Example:

@ECHO OFF
REM *** DOS batch file
ECHO. Type something to test the READLINE function:
READLINE
ECHO. You typed: %0 %1 %2 %3 %4 %5 %6 %7 %8 %9

Source: The OS/2 WARP Survival Guide

The RENAME command

Use the undocumented switch /S of the DOS command RENAME to rename directories in DOS sessions also. The OS/2 command RENAME can rename directories without this switch.

Source: The OS/2 WARP Survival Guide

The SET command

Use the undocumented switch /S of the DOS command SET to get the number of remaining free bytes in the DOS environment. Note that you do not need this option in OS/2 sessions because the size of the environment of OS/2 sessions is dynamic.

Source: The OS/2 WARP Survival Guide

The TIME command

Use the undocumented switch /N of the DOS command TIME to display the time on the screen without prompting to enter a new time.

Source: The OS/2 WARP Survival Guide

The TRUENAME command

Use the nearly undocumented DOS command TRUENAME to display the true name of a file on the screen. For example the command

truename c:\os2\dll\..\install\..\..\config.sys

will print C:\CONFIG.SYS to the screen. ("nearly undocumented" because TRUENAME /? works but you can't find anything about it in the online help)

Source: The OS/2 WARP Survival Guide

In OS/2 REXX programs you may use the function STREAM with the third parameter set to QUERY EXISTS to achieve the same result for existing files.

Reserved directory & file names

You cannot create a directory or file called \PIPE. This is a necessary restriction, because the names of named pipes all begin with \PIPE\ (and pipes are handled like ordinary files in OS/2).

Also you cannot create or access directories or files with the names of loaded device driver in any directory. Some standard device driver for OS/2 are the following (there may be others not listed here):

Device name Device driver
APM$
CLOCK$ CLOCK01.SYS
COM1 - COM4 COM.SYS
CON
DOS$ DOS.SYS
IBMKBD$ IBMKBD.SYS
KBD$ IBMKBD.SYS
LOG$ LOG.SYS
NUL (NUL device)
LPT1 - LPT9 PRINT01.SYS
MOUSE$ MOUSE.SYS
OEMHLP$ SCREEN01.SYS
PCLOGIC$ PCLOGIC.SYS
POINTER$ POINTDD.SYS
PRN PRINT01.SYS
RESERVE$ RESERVE.SYS
RESMGR$ TESTCFG.SYS
SCREEN$ SCREEN01.SYS
TIMER$ CLOCK01.SYS
SINGELQ$ PMDD.SYS

In general you should not use a file or directory name ending with a dollar sign.

Warning

You can create a directory or file with the name of a device if the necessary device driver for the device is not loaded. But be aware, that you cannot access that file or directory anymore after installing the device driver again!

(see also Output & Input control, Reserved names for files in REXX, Check if a name describes a device or a file and Using the CLOCK$ device.)

If you really need to create or delete a directory with the name of a device you can use the following trick:

REM *** Create a directory called APM$ in the current directory
md APM$\.

REM *** Delete a directory called APM$ in the current directory
rd APM$\.

REM *** Create a directory called CLOCK$ in the root directory
md \CLOCK$\.

REM *** Delete a directory called CLOCK$ in the root directory
rd \CLOCK$\.