[net.sources] VMS command interpreter.

abbes@usl.UUCP ( ) (05/20/86)

I am posting in  the  next  article  a  command  interpreter  for
Vax/VMS,  offering  facilities such as those found in the Unix C-
shell.

It is a simple (1647 lines) program, written as a  collection  of
DCL procedures. The doc is under the form of help files.

You can certainly make it faster if you rewrite it in a  compiled
language  such as "C" or Ada, though it works as is and is not so
slow.

abbes@usl.UUCP ( ) (05/20/86)

The source (and doc) follows: 1700 lines.


                    
===============================================================
                   INSTALLATION GUIDE


1) This command interpreter consists of a lot  of  small  command
procedures,  organized  into directories. Find a place to put it,
such as a directory "tools" or the home directory of a  dedicated
user.

Create a directory "bin" for the general  purpose  command  files
(profile.com,  def0.com,  etc.)  and a directory "vmx" with three
subdirectories: "bin" for the .com files,  "help"  for  the  help
files, "users" for the init files of all the users.

2) Make sure that any user willing to use it has correct  logical
assignments in his login.com (and other init files run at login).

>  !--> file LOGIN.COM
>  set noverify
>  assign disk0:[root.z.bin] bin
>  @bin:profile                   -- other logical assignments
>  vmx  -- on the last line
>  !<-- end LOGIN.COM

3) When you install this set of procedures for  the  first  time,
make sure to comment out the lines

!on control_y then goto ...

So that if anything goes wrong, you can still abort everything by
^Y.

4) After you have tried all possible sorts  of  commands,  remove
the exclam marks so that control_y is handled.

5) There is a command that any user can use to  create  his  init
file.

=================================================================



--> list of the files that make up VMX and SHELL

========== ========== ========== ========== ========== ========== 

DISK0:[ROOT.Z]BIN.DIR                2
DISK0:[ROOT.Z]LOGIN.COM              1      
DISK0:[ROOT.Z]VMX.DIR                1      

Total of 3 files, 4 blocks.

========== ========== ========== ========== ========== ========== 

DISK0:[ROOT.Z.BIN]CD.COM             1      
DISK0:[ROOT.Z.BIN]COPY1.PAS          1      
DISK0:[ROOT.Z.BIN]COPY2.PAS          1      

DISK0:[ROOT.Z.BIN]CWD.COM            1      
DISK0:[ROOT.Z.BIN]DEF0.COM           1      
DISK0:[ROOT.Z.BIN]DEF1.COM           3      

DISK0:[ROOT.Z.BIN]DEF2.COM           1      
DISK0:[ROOT.Z.BIN]DEF3.COM           1      
DISK0:[ROOT.Z.BIN]EDTINI.EDT         1      

DISK0:[ROOT.Z.BIN]MKDIR.COM          1      
DISK0:[ROOT.Z.BIN]PROFILE.COM        1      
DISK0:[ROOT.Z.BIN]RM.COM             1      

DISK0:[ROOT.Z.BIN]RMDIR.COM          1      
DISK0:[ROOT.Z.BIN]SLEEP.COM          1      
DISK0:[ROOT.Z.BIN]UP.COM             1      

Total of 15 files, 17 blocks.

========== ========== ========== ========== ========== ========== 

DISK0:[ROOT.Z.VMX]BIN.DIR            1      
DISK0:[ROOT.Z.VMX]HELP.DIR           1      
DISK0:[ROOT.Z.VMX]USERS.DIR          1      

Total of 3 files, 3 blocks.

========== ========== ========== ========== ========== ========== 

DISK0:[ROOT.Z.VMX.BIN]DEBUG.COM      1      
DISK0:[ROOT.Z.VMX.BIN]DELETE.COM     1      
DISK0:[ROOT.Z.VMX.BIN]ECHERR.COM     3

DISK0:[ROOT.Z.VMX.BIN]ECHO.COM       3      
DISK0:[ROOT.Z.VMX.BIN]EDIX.COM       2      
DISK0:[ROOT.Z.VMX.BIN]EDTINI.EDT     1      

DISK0:[ROOT.Z.VMX.BIN]EXEC.COM       2      
DISK0:[ROOT.Z.VMX.BIN]FORK.COM       6      
DISK0:[ROOT.Z.VMX.BIN]HELP.COM       2      

DISK0:[ROOT.Z.VMX.BIN]HISTORY.COM    2
DISK0:[ROOT.Z.VMX.BIN]INITVMX.COM    4      
DISK0:[ROOT.Z.VMX.BIN]NODEBUG.COM    1      

DISK0:[ROOT.Z.VMX.BIN]PIPE.COM       13      
DISK0:[ROOT.Z.VMX.BIN]SEND.COM       5      
DISK0:[ROOT.Z.VMX.BIN]SH.COM         1      

DISK0:[ROOT.Z.VMX.BIN]SHELL.COM      12      
DISK0:[ROOT.Z.VMX.BIN]VMX.COM        10      
DISK0:[ROOT.Z.VMX.BIN]VMXSHELL.COM   1      

Total of 18 files, 70 blocks.

========== ========== ========== ========== ========== ========== 


DISK0:[ROOT.Z.VMX.HELP]DEBUG.HLP     2      
DISK0:[ROOT.Z.VMX.HELP]ECHO.HLP      1      
DISK0:[ROOT.Z.VMX.HELP]EDIX.HLP      2      

DISK0:[ROOT.Z.VMX.HELP]FILES.HLP     2      
DISK0:[ROOT.Z.VMX.HELP]FORK.HLP      2      
DISK0:[ROOT.Z.VMX.HELP]GRAMMAR.HLP   2      

DISK0:[ROOT.Z.VMX.HELP]HELP.HLP      1      
DISK0:[ROOT.Z.VMX.HELP]HS.HLP        2      
DISK0:[ROOT.Z.VMX.HELP]NICE.HLP      2      

DISK0:[ROOT.Z.VMX.HELP]PIPE.HLP      2      
DISK0:[ROOT.Z.VMX.HELP]SEMANTIX.HLP  1      
DISK0:[ROOT.Z.VMX.HELP]SEND.HLP      1      

DISK0:[ROOT.Z.VMX.HELP]SH.HLP        2      
DISK0:[ROOT.Z.VMX.HELP]SHELL.HLP     2      
DISK0:[ROOT.Z.VMX.HELP]SYMBOL.HLP    2      

DISK0:[ROOT.Z.VMX.HELP]TOPICS.HLP    1      
DISK0:[ROOT.Z.VMX.HELP]VMX.HLP       3      
DISK0:[ROOT.Z.VMX.HELP]VMXSHELL.HLP  2      

Total of 18 files, 32 blocks.

========== ========== ========== ========== ========== ========== 

DISK0:[ROOT.Z.VMX.USERS]JACK.INI     4      
DISK0:[ROOT.Z.VMX.USERS]JEAN.INI     4      
DISK0:[ROOT.Z.VMX.USERS]JOHN.INI     4      

Total of 3 files, 12 blocks.

========== ========== ========== ========== ========== ========== 

Grand total of 6 directories, 60 files, 138 blocks.

========== ========== ========== ========== ========== ========== 

<-- end of list



!--> file LOGIN.COM  ---------- ---------- ---------- --------->
$
set noverify
set term/dev=vt100
$
assign disk0:[root.z.bin] bin
@bin:profile
vmx
!<-- end  ---------- ---------- ---------- ---------- <---------

!--> file DEBUG.COM  ---------- ---------- ---------- --------->
is_debug_'p1'  :== true
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file DELETE.COM ---------- ---------- ---------- --------->
if f$search(p1) -
  .nes. "" then delete 'p1'
$
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file ECHERR.COM ---------- ---------- ---------- --------->
arg_string     = "''p1' ''p2' ''p3' ''p4' ''p5' ''p6' ''p7' ''p8'"
argument_line := 'arg_string
LOOP_ON_BLANK:
  length         = f$length(argument_line)
  position       = f$locate("\B", argument_line)
  exists_blank   = position .ne. length
  if .not. exists_blank then goto EXIT_LOOP_ON_BLANK
  argument_line  = f$extract(0, position, argument_line) + " " + -
                   f$extract(2 + position, length, argument_line)
END_LOOP_ON_BLANK: goto LOOP_ON_BLANK
EXIT_LOOP_ON_BLANK:

LOOP_ON_NEWLINE:
  length         = f$length(argument_line)
  position       = f$locate("\N", argument_line)
  exists_newline = position .ne. length
  first_line     = f$extract(0, position, argument_line)
  argument_line  = f$extract(2 + position, length, argument_line)
  write sys$error first_line
  if .not. exists_newline then exit
END_LOOP_ON_NEWLINE: goto LOOP_ON_NEWLINE
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file ECHO.COM   ---------- ---------- ---------- --------->
arg_string     = "''p1' ''p2' ''p3' ''p4' ''p5' ''p6' ''p7' ''p8'"
argument_line := 'arg_string
LOOP_ON_BLANK:
  length         = f$length(argument_line)
  position       = f$locate("\B", argument_line)
  exists_blank   = position .ne. length
  if .not. exists_blank then goto EXIT_LOOP_ON_BLANK
  argument_line  = f$extract(0, position, argument_line) + " " + -
                   f$extract(2 + position, length, argument_line)
END_LOOP_ON_BLANK: goto LOOP_ON_BLANK
EXIT_LOOP_ON_BLANK:

LOOP_ON_NEWLINE:
  length         = f$length(argument_line)
  position       = f$locate("\N", argument_line)
  exists_newline = position .ne. length
  first_line     = f$extract(0, position, argument_line)
  argument_line  = f$extract(2 + position, length, argument_line)
  write sys$output first_line
  if .not. exists_newline then exit
END_LOOP_ON_NEWLINE: goto LOOP_ON_NEWLINE
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file EDIX.COM   ---------- ---------- ---------- --------->
user_id        = f$getjpi("", "username")
user_name     := 'user_id
user_init_file = "vmx_users_dir:" + user_name + ini_extension

if f$search(user_init_file) -
  .eqs. "" then vmx_put_line "edix: creating ''user_init_file'"
if f$search(user_init_file) -
  .eqs. "" then wait 0:0:3
if f$search(user_init_file) -
  .eqs. "" then vmx_copy vmx_dir:initvmx.com 'user_init_file'
$
vmx_put_line "edix: opening  ''user_init_file'"
vmx_put_line "      type ^Z, then QUIT or EXIT to exit the editor"
wait 0:0:3
input_mode = f$logical("sys$input")
if input_mode .nes. "SYS$COMMAND:" then -
  define/user_mode sys$input sys$command:
vmx_edit 'user_init_file'
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file EDTINI.EDT ---------- ---------- ---------- --------->
set mode change
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file EXEC.COM   ---------- ---------- ---------- --------->
is_verify = f$verify(is_debug_exec)

$! procedure EXEC (P1 : STRING  := "") is

on warning      then goto WHEN_WARNING
on error        then goto WHEN_ERROR
on severe_error then goto WHEN_SEVERE_ERROR
on control_y    then goto WHEN_CONTROL_Y

argument_string := 'p1'
command_line = 'argument_string
'command_line 'p2' 'p3' 'p4' 'p5' 'p6' 'p7' 'p8'
$
EXCEPTION: goto RETURN
WHEN_WARNING:
  vmx_put_error "exec: warning"
  exit
WHEN_ERROR:
  vmx_put_error "exec: error"
  exit
WHEN_SEVERE_ERROR:
  vmx_put_error "exec: severe_error"
  exit
WHEN_CONTROL_Y:
  vmx_put_error "exec: control_y"

$! end EXEC;

RETURN: is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file FORK.COM   ---------- ---------- ---------- --------->
is_verify = f$verify(is_debug_fork)

$! procedure FORK (P1 : STRING  := "";
$!                 P2 : NATURAL := 0) is

is_new_input  = false
is_new_output = false
is_to_fork    = false

on warning       then goto WHEN_WARNING
on error         then goto WHEN_ERROR
on severe_error  then goto WHEN_SEVERE_ERROR
on control_y     then goto WHEN_CONTROL_Y

argument_string := 'p1'
fork_line        = 'argument_string

second_argument  = 'p2
if p2 .eqs. "" then second_argument = "000"
count_string    := 'second_argument

initial_name  = "fork" + count_string
fork_out_file = initial_name + out_extension
fork_com_file = initial_name + vmx_extension

message_in    = "fork: <" + in_file
message_out   = "fork: >" + fork_out_file

LOOP:
  first_char = f$extract(0, 1, fork_line)
  is_special = first_char .eqs. "<" .or. first_char .eqs. ">" .or. -
               first_char .eqs. "&" .or. first_char .eqs. " "
  if .not. is_special then goto EXIT_LOOP

  if first_char .eqs. "<" then is_new_input  = true
  if first_char .eqs. ">" then is_new_output = true
  if first_char .eqs. "&" then is_to_fork    = true

  fork_line = f$extract(1, f$length(fork_line), fork_line)
  if first_char .eqs. "&" then goto EXIT_LOOP
END_LOOP: goto LOOP
EXIT_LOOP: if .not. is_to_fork then goto RETURN

if is_new_input  then vmx_put_line message_in
if is_new_output then vmx_put_line message_out

first_def  = "first_argument := " + """""""" + fork_line + """"""""
first_arg  = "line_arg        = 'first_argument"
second_def = "second_argument = " + count_string
second_arg = "count_arg      := 'second_argument"
shell_call = "vmx_shell line_arg count_arg"

open/write logical_file 'fork_com_file
     write logical_file first_def
     write logical_file first_arg
     write logical_file second_def
     write logical_file second_arg
     write logical_file shell_call
close      logical_file

fork_line := 'vmx_spawn
if is_new_input  then fork_line = fork_line + "/input="  + in_file
if is_new_output then fork_line = fork_line + "/output=" + fork_out_file
fork_line = fork_line + " @" + fork_com_file
vmx_exec fork_line
$
vmx_delete 'fork_com_file.*
$
EXCEPTION: goto RETURN
WHEN_WARNING:
  vmx_put_error "fork: warning"
  goto RETURN
WHEN_ERROR:
  vmx_put_error "fork: error"
  goto RETURN
WHEN_SEVERE_ERROR:
  vmx_put_error "fork: severe_error"
  goto RETURN
WHEN_CONTROL_Y:
  vmx_put_error "fork: control_y"

$! end FORK;

RETURN: is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file HELP.COM   ---------- ---------- ---------- --------->
reply = p1
if reply .nes. "" then goto HAS_REPLY
ty vmx_help_dir:help.hlp
$
LOOP:
ty vmx_help_dir:topics.hlp
$
inquire/nopunctuation reply "Topic? "
if reply .eqs. "" then exit

HAS_REPLY:
help_file = "vmx_help_dir:" + reply + ".hlp"
if f$search(help_file) -
  .nes. "" then ty 'help_file'
$
if f$search(help_file) -
  .eqs. "" then vmx_put_line "  Sorry, no documentation on ''reply'"
$
END_LOOP: goto LOOP
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file HISTORY.COM --------- ---------- ---------- --------->
is_verify = f$verify(is_debug_history)

$! procedure HISTORY (P1 : NATURAL) is

index = count - p1
if p1 .eqs. "" .or. p1 .gt. count then index = init_count - 1
if p1 .eqs. "" .and. count .ge. max_lines_for_hs - 1 then -
  index = count - max_lines_for_hs 
if p1 .nes. "" .and. 0  .ge. p1 then goto RETURN
  
LOOP_HISTORY:
  index = 1 + index
  history_string = tab + "when " + f$string(index) + -
    " => " + tab + string_array'index
  vmx_put_line history_string

  modulo = index - modulo_for_hs * (index / modulo_for_hs)
  if modulo .eq. modulo_for_hs - 1 then vmx_put_line ""
  if index .ge. count then goto RETURN
END_LOOP_HISTORY: goto LOOP_HISTORY

$! end HISTORY;

RETURN: is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file INITVMX.COM --------- ---------- ---------- --------->
vmx_extension    :== ".vmx"
ini_extension    :== ".ini"
com_extension    :== ".com"
job_extension    :== ".job"

in_file          :== "temp.in"
out_extension    :== ".out"
err_extension    :== ".err"
res_extension    :== ".res"

hx               :== @vmx_dir:help
hs               :== @vmx_dir:history
edix             :== @vmx_dir:edix
debug            :== @vmx_dir:debug
no_debug         :== @vmx_dir:nodebug

sh               :== @vmx_dir:sh
vmx_shell        :== @vmx_dir:vmxshell
vmx_send         :== @vmx_dir:send
vmx_fork         :== @vmx_dir:fork
vmx_pipe         :== @vmx_dir:pipe
vmx_exec         :== @vmx_dir:exec
vmx_delete       :== @vmx_dir:delete

vmx_nice         :== submit/noprinter/delete/noid/log_file=[]'res_extension
vmx_spawn        :== spawn/nowait/nolog
vmx_rename       :== rename
vmx_copy         :== copy
vmx_edit         :== edit/edt/command=vmx_dir:edtini.edt

vmx_put_line     :== write sys$error
vmx_put_help     :== if is_help_message    then write sys$error
vmx_put_warning  :== if is_warning_message then write sys$error
vmx_put_error    :== if is_error_message   then write sys$error

max_control_y       :== 3
max_count           :== 333
init_count          :== 0
max_lines_for_hs    :== 15

constant_ps1        :== "_"
ps1                 :== "''constant_ps1'"
constant_tab        :== "     "
tab                 :== "''constant_tab'"

false               :== 0
true                :== 1

is_help_message      :== true
is_warning_message   :== true
is_error_message     :== true
is_vmx_wanted        :== true

is_debug_vmx         :== false
is_debug_shell       :== false
is_debug_pipe        :== false
is_debug_fork        :== false
is_debug_send        :== false
is_debug_nice        :== false
is_debug_exec        :== false
is_debug_history     :== false
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file NODEBUG.COM --------- ---------- ---------- --------->
is_debug_'p1'  :== false
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file PIPE.COM   ---------- ---------- ---------- --------->
is_verify = f$verify(is_debug_pipe)

$! procedure PIPE (P1 : STRING  := "";
$!                 P2 : NATURAL := 0) is

pipe_max_control_y = 3
control_y_count    = 0
pipe_count         = -1

on warning       then goto WHEN_WARNING
on error         then goto WHEN_ERROR
on severe_error  then goto WHEN_SEVERE_ERROR
on control_y     then goto WHEN_CONTROL_Y

argument_string := 'p1'
command_line     = 'argument_string

second_argument  = 'p2
if p2 .eqs. "" then second_argument = "000"
count_string    := 'second_argument

initial_name = "pip" + count_string + "x"
message_in   = "pipe: <" + in_file

in_pipe  = "inpip"  + count_string + vmx_extension
out_pipe = "outpip" + count_string + vmx_extension
com_pipe = "compip" + count_string + vmx_extension

deassign_sys_input = "deassign sys$input"
pipe_sys_command   = "assign/user_mode " + in_pipe + " sys$command"
assign_sys_command = "assign/user_mode " + in_file + " sys$command"
assign_sys_input   = "assign/user_mode sys$command: sys$input"

open_sys_error     = "open/write sys$error: "
define_sys_error   = "define sys$error sys$error:"
deassign_sys_error = "$deassign sys$error"
close_sys_error    = "$close sys$error:"

is_a_pipe = false

LOOP:
  was_a_pipe    = is_a_pipe
  length_line   = f$length(command_line)
  position_pipe = f$locate("|", command_line)
  is_a_pipe     = position_pipe .ne. length_line  

  p = length_line - position_pipe - 1
  first_command = f$extract(0, position_pipe, command_line)
  command_line  = f$extract(1 + position_pipe, p, command_line)
  if first_command .eqs. "" then goto END_EXEC_COM
  
  open/write pipe_file 'com_pipe
  if was_a_pipe then write pipe_file pipe_sys_command
  write pipe_file deassign_sys_input
  write pipe_file assign_sys_input
  command_all = first_command

  OUTER_LOOP:
    is_new_input   = false
    is_new_output  = false
    is_new_error   = false
    is_a_job       = false

    pipe_count     = 1 + pipe_count
    prefix_file    = initial_name  + f$string(pipe_count)
    com_file       = prefix_file + vmx_extension
    job_file       = prefix_file + job_extension
    out_file       = prefix_file + out_extension
    err_file       = prefix_file + err_extension
    open_sys_error_string = open_sys_error + err_file

    length_all     = f$length(command_all)
    position       = f$locate(";", command_all)
    is_multiple    = position .ne. length_all

    p = length_line - position - 1
    command_one    = f$extract(0, position, command_all)
    command_all    = f$extract(1 + position, p, command_all)
    if command_one .eqs. "" then goto END_MULTIPLE_COMMAND

    INNER_LOOP:
      first_char = f$extract(0, 1, command_one)
      is_special = first_char .eqs. "<" .or. first_char .eqs. ">" .or. -
                   first_char .eqs. "?" .or. first_char .eqs. "+" .or. -
                   first_char .eqs. " "
      if .not. is_special then goto EXIT_INNER_LOOP
  
      if first_char .eqs. "<" then is_new_input  = true
      if first_char .eqs. ">" then is_new_output = true
      if first_char .eqs. "?" then is_new_error  = true
      if first_char .eqs. "+" then is_a_job      = true

      command_one = f$extract(1, f$length(command_one), command_one)
    END_INNER_LOOP: goto INNER_LOOP
    EXIT_INNER_LOOP:
    if .not. is_new_output .and. .not. is_a_job then goto STANDARD_OUTPUT

    REDIRECT_OUTPUT:
    change_dir = "$set def " + f$logical("sys$disk") + f$directory()
    open/write logical_file 'com_file
         if is_a_job then write logical_file change_dir
         write logical_file "$''command_one'"
    close      logical_file
    if is_new_output then command_one = "@" + com_file + "/out=" + out_file
    if is_a_job then vmx_rename 'com_file 'job_file
$   if is_a_job then command_one = "vmx_nice " + job_file

    STANDARD_OUTPUT:
    message_out = "pipe: >" + out_file
    message_err = "pipe: ?" + err_file

    if is_new_input  then deassign sys$input
    if is_new_input  then assign/user_mode in_file sys$input

    if is_new_input  then vmx_put_line message_in
    if is_new_output then vmx_put_line message_out
    if is_new_error  then vmx_put_line message_err

    if is_new_error  then write pipe_file open_sys_error_string
    if is_new_error  then write pipe_file define_sys_error
    write pipe_file "$''command_one'"
    if is_new_error  then write pipe_file deassign_sys_error
    if is_new_error  then write pipe_file close_sys_error
    if .not. is_multiple then goto EXIT_OUTER_LOOP
  END_MULTIPLE_COMMAND:
    if command_all .eqs. "" then goto EXIT_OUTER_LOOP
  END_OUTER_LOOP: goto OUTER_LOOP
  EXIT_OUTER_LOOP:
  close pipe_file

  EXEC_COM:
  command_one = "@''com_pipe'"
  if is_a_pipe then command_one = command_one + "/out=''out_pipe'"
  vmx_exec command_one
$ if is_a_pipe then vmx_rename 'out_pipe 'in_pipe
$ vmx_delete 'initial_name'*'vmx_extension'.*
$ vmx_delete 'com_pipe.*
$ if .not. is_a_pipe then vmx_delete 'in_pipe.*
$ if .not. is_a_pipe then goto RETURN
  END_EXEC_COM:
  if command_line .eqs. "" then goto RETURN

  EXCEPTION: goto END_LOOP
  WHEN_WARNING:
    vmx_put_error "pipe: warning"
    goto END_LOOP
  WHEN_ERROR:
    vmx_put_error "pipe: error"
    goto END_LOOP
  WHEN_SEVERE_ERROR:
    vmx_put_error "pipe: severe_error"
    goto END_LOOP
  WHEN_CONTROL_Y:
    control_y_string = "pipe: control_y"
    control_y_count = 1 + control_y_count
    if control_y_count .eq. 1 then -
      control_y_string = control_y_string + " (only " + -
      pipe_max_control_y + " allowed)"
    if control_y_count .eq. pipe_max_control_y - 1 then -
      control_y_string = control_y_string + " (next is last)"
     if control_y_count .eq. pipe_max_control_y then -
      control_y_string = control_y_string + " (last)"
     if control_y_count .gt. pipe_max_control_y then -
      control_y_string = control_y_string + " (exit)"
    vmx_put_error control_y_string
    if control_y_count .gt. pipe_max_control_y then goto RETURN
END_LOOP: goto LOOP

$! end PIPE;

RETURN: is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file SEND.COM   ---------- ---------- ---------- --------->
is_verify = f$verify(is_debug_send)

$! procedure SEND (P1 : STRING  := "";
$!                 P2 : NATURAL := 0) is

on warning       then goto WHEN_WARNING
on error         then goto WHEN_ERROR
on severe_error  then goto WHEN_SEVERE_ERROR
on control_y     then goto WHEN_CONTROL_Y

argument_string := 'p1'
send_line        = 'argument_string

second_argument  = 'p2
if p2 .eqs. "" then second_argument = "000"
count_string    := 'second_argument

is_to_send       = false
initial_name     = "send" + count_string
send_com_file    = initial_name + vmx_extension

LOOP:
  first_char = f$extract(0, 1, send_line)
  is_special = first_char .eqs. "^" .or. first_char .eqs. " "
  if .not. is_special then goto EXIT_LOOP

  if first_char .eqs. "^" then is_to_send = true

  send_line = f$extract(1, f$length(send_line), send_line)
END_LOOP: goto LOOP
EXIT_LOOP: if .not. is_to_send then goto RETURN

change_dir = "set def " + f$logical("sys$disk") + f$directory()
first_def  = "first_argument := " + """""""" + send_line + """"""""
first_arg  = "line_arg        = 'first_argument"
second_def = "second_argument = " + count_string
second_arg = "count_arg      := 'second_argument"
shell_call = "vmx_shell line_arg count_arg"

open/write logical_file 'send_com_file
     write logical_file change_dir
     write logical_file first_def
     write logical_file first_arg
     write logical_file second_def
     write logical_file second_arg
     write logical_file shell_call
close      logical_file

send_line := "vmx_nice ''send_com_file'"
vmx_exec send_line
$
EXCEPTION: goto RETURN
WHEN_WARNING:
  vmx_put_error "send: warning"
  goto RETURN
WHEN_ERROR:
  vmx_put_error "send: error"
  goto RETURN
WHEN_SEVERE_ERROR:
  vmx_put_error "send: severe_error"
  goto RETURN
WHEN_CONTROL_Y:
  vmx_put_error "send: control_y"

$! end SEND;

RETURN: is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file SH.COM     ---------- ---------- ---------- --------->
from_vmx = 0
@vmx_dir:shell 'p1' 'from_vmx' 'p2'
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file SHELL.COM  ---------- ---------- ---------- --------->
is_verify = f$verify(is_debug_shell)

$! procedure SHELL (P1 : STRING  := "";
$!                  P2 : BOOLEAN;
$!                  P3 : NATURAL := 0) is

on warning       then goto WHEN_WARNING
on error         then goto WHEN_ERROR
on severe_error  then goto WHEN_SEVERE_ERROR
on control_y     then goto WHEN_CONTROL_Y

shell_max_control_y = 3
control_y_count     = 0
shell_count         = -1

argument_string := 'p1'
if p2       then command_line  = 'argument_string
if .not. p2 then command_line := 'p1'

if p3 .nes. "" then second_argument = 'p3
if p3 .eqs. "" then second_argument = "0"
count_string    := 'second_argument

initial_name = count_string
message_in   = "shell: <" + in_file

LOOP_ON_SPACE:
  length         = f$length(command_line)
  position       = f$locate("\S", command_line)
  exists_space   = position .ne. length
  if .not. exists_space then goto EXIT_LOOP_ON_SPACE
  command_line  = f$extract(0, position, command_line) + " " + -
                  f$extract(2 + position, length, command_line)
END_LOOP_ON_SPACE: goto LOOP_ON_SPACE
EXIT_LOOP_ON_SPACE:

length_line  = f$length(command_line)

SEND_A_JOB:
  position = f$locate("^", command_line)
  is_a_job = position .ne. length_line 
  if is_a_job then vmx_send command_line count_string
  if is_a_job then goto RETURN

FORK_A_PROCESS:
  position     = f$locate("&", command_line)
  is_a_process = position .ne. length_line 
  if is_a_process then vmx_fork command_line count_string
  if is_a_process then goto RETURN

RUN_A_PIPE: 
  position  = f$locate("|", command_line)
  is_a_pipe = position .ne. length_line 
  if is_a_pipe then vmx_pipe command_line count_string
  if is_a_pipe then goto RETURN

LOOP:
  on warning       then goto WHEN_WARNING
  on error         then goto WHEN_ERROR
  on severe_error  then goto WHEN_SEVERE_ERROR
  on control_y     then goto WHEN_CONTROL_Y

  is_new_input  = false
  is_new_output = false
  is_new_error  = false
  is_a_job      = false

  shell_count   = 1 + shell_count
  prefix_file   = initial_name + "x" + f$string(shell_count)
  com_file      = prefix_file + vmx_extension
  out_file      = prefix_file + out_extension
  err_file      = prefix_file + err_extension

  length_line   = f$length(command_line)
  position      = f$locate(";", command_line)
  is_multiple   = position .ne. length_line  
  p = length_line - position - 1
  first_command = f$extract(0, position, command_line)
  command_line  = f$extract(1 + position, p, command_line)
  if first_command .eqs. "" then goto END_EXEC_COM

  INNER_LOOP:
    first_char = f$extract(0, 1, first_command)
    is_special = first_char .eqs. "<" .or. first_char .eqs. ">" .or. -
                 first_char .eqs. "?" .or. first_char .eqs. "+" .or. -
                 first_char .eqs. " "
    if .not. is_special then goto EXIT_INNER_LOOP

    if first_char .eqs. "<" then is_new_input  = true
    if first_char .eqs. ">" then is_new_output = true
    if first_char .eqs. "?" then is_new_error  = true
    if first_char .eqs. "+" then is_a_job      = true

    first_command = f$extract(1, f$length(first_command), first_command)
  END_INNER_LOOP: goto INNER_LOOP
  EXIT_INNER_LOOP:
  if .not. is_new_output .and. .not. is_a_job then goto STANDARD_OUTPUT

  REDIRECT_OUTPUT:
  change_dir = "set def " + f$logical("sys$disk") + f$directory()
  open/write logical_file 'com_file
       if is_a_job then write logical_file change_dir
       write logical_file first_command
  close      logical_file
  if is_new_output then first_command = "@" + com_file + "/out=" + out_file
  if is_a_job then first_command = "vmx_nice " + com_file

  STANDARD_OUTPUT:
  message_out = "shell: >" + out_file
  message_err = "shell: ?" + err_file

  if is_new_input  then deassign sys$input
  if is_new_input  then assign/user_mode in_file sys$input

  if is_new_input  then vmx_put_line message_in
  if is_new_output then vmx_put_line message_out
  if is_new_error  then vmx_put_line message_err

  if is_new_error  then open/write sys$error: 'err_file'
  if is_new_error  then define sys$error sys$error:

  EXEC_COM:
  vmx_exec first_command
$
  if is_new_error then deassign sys$error
  if is_new_error then close sys$error:
  if is_new_output .and. .not. is_a_job then vmx_delete 'com_file.*
$
  if .not. is_multiple then goto RETURN
  END_EXEC_COM:
  if command_line .eqs. "" then goto RETURN

  EXCEPTION: goto END_LOOP
  WHEN_WARNING:
    vmx_put_error "shell: warning"
    goto END_LOOP
  WHEN_ERROR:
    vmx_put_error "shell: error"
    goto END_LOOP
  WHEN_SEVERE_ERROR:
    vmx_put_error "shell: severe_error"
    goto END_LOOP
  WHEN_CONTROL_Y:
    control_y_string = "shell: control_y"
    control_y_count = 1 + control_y_count
    if control_y_count .eq. 1 then -
      control_y_string = control_y_string + " (only " + -
      shell_max_control_y + " allowed)"
    if control_y_count .eq. shell_max_control_y - 1 then -
      control_y_string = control_y_string + " (next is last)"
    if control_y_count .eq. shell_max_control_y then -
      control_y_string = control_y_string + " (last)"
    if control_y_count .gt. shell_max_control_y then -
      control_y_string = control_y_string + " (exit)"
    vmx_put_error control_y_string
    if control_y_count .gt. shell_max_control_y then goto RETURN
END_LOOP: goto LOOP
RETURN: 
$! end SHELL;
is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file VMX.COM    ---------- ---------- ---------- --------->
INIT:
  vmx_init_file  = "vmx_dir:initvmx.com"
  if f$search(vmx_init_file) -
    .eqs. "" then exit
  @'vmx_init_file
$
  user_id        = f$getjpi("", "username")
  user_name     := 'user_id
  user_init_file = "vmx_users_dir:" + user_name + ini_extension
  if f$search(user_init_file) -
    .eqs. "" then -
    vmx_put_warning "vmx: can't open ''user_init_file'; use EDIX"
  if f$search(user_init_file) -
    .nes. "" then @'user_init_file
$
END_INIT:

modulo_for_hs  = max_lines_for_hs / 5
control_y_count = 0
count           = init_count - 1

if f$mode() .nes. "INTERACTIVE" then exit
if .not. is_vmx_wanted          then exit
vmx_put_help "vmx: type HX for help, EX to exit"

LOOP:
  is_verify = f$verify(is_debug_vmx)

  $! procedure VMX is

  on warning       then goto WHEN_WARNING
  on error         then goto WHEN_ERROR
  on severe_error  then goto WHEN_SEVERE_ERROR
  on control_y     then goto WHEN_CONTROL_Y

  if count .ge. max_count then count = init_count - 1
  count        = 1 + count
  count_string = f$string(count)

  input_mode = f$logical("sys$input")
  if input_mode .nes. "SYS$COMMAND:" then -
    define/user_mode sys$input sys$command:

  global_prompt      = f$string('count) + PS1
  prompt            := 'global_prompt
  string_array'count = "control_y"
  inquire/nopunctuation command_line "''prompt' "

  string_array'count = command_line
  first_char  = f$extract(0, 1, command_line)
  is_a_digit  = "0" .les. first_char .and. first_char .les. "9"
  is_to_edit  = first_char .eqs. "/"
  is_previous = first_char .eqs. "\"

  if is_previous then index = count - 1
  if is_previous then command_line = string_array'index
  if is_a_digit  then command_line = string_array'command_line
  if is_a_digit .or. is_previous then string_array'count = command_line
  if is_a_digit .or. is_previous then vmx_put_line "''prompt' ''command_line'"
  if .not. is_to_edit then goto NOT_TO_EDIT

  BLOCK_EDIT:
    old_com      = "oldcom''count_string'" + vmx_extension
    new_com      = "newcom''count_string'" + vmx_extension
    command_line = f$extract(1, f$length(command_line), command_line)
    if command_line .eqs. "" then position = count - 1
    if command_line .nes. "" then position = command_line
    command_line = string_array'position
    open/write logical_file 'new_com
    close      logical_file
    open/write logical_file 'old_com
         write logical_file command_line
    close      logical_file
    command_line = ""
    vmx_edit/nojournal/output='new_com 'old_com
$   open/read logical_file 'new_com
         read logical_file command_line /end_of_file=WHEN_END_OF_FILE
    input_mode = f$logical("sys$input")
    if input_mode .nes. "SYS$COMMAND:" then -
      define/user_mode sys$input sys$command:
  EXCEPTION:
  WHEN_END_OF_FILE:
    close logical_file
    string_array'count = command_line
    vmx_delete 'old_com.*
$   vmx_delete 'new_com.*
$   vmx_put_line "''prompt' ''command_line'"
  END_BLOCK_EDIT:

  NOT_TO_EDIT:
  length_line        = f$length(command_line)
  position_semicolon = f$locate(";", command_line)
  exists_semicolon   = position_semicolon .ne. length_line 
  is_for_shell       = exists_semicolon
  if is_for_shell then goto CALL_THE_SHELL

  if command_line .eqs. "EX"   then goto RETURN
  if command_line .eqs. "INIX" then count = INIT_COUNT - 1
  if command_line .eqs. "INYX" then control_y_count = 0

  if command_line .eqs. "INIX" .or. -
     command_line .eqs. "INYX" then goto END_LOOP

VMS_COMMAND:
  vmx_exec command_line
$END_VMS_COMMAND: goto END_LOOP

CALL_THE_SHELL:
  vmx_shell command_line count_string
END_CALL_THE_SHELL: goto END_LOOP

EXCEPTION:
  WHEN_WARNING:
    vmx_put_error "vmx: warning"
    goto END_LOOP
  WHEN_ERROR:
    vmx_put_error "vmx: error"
    goto END_LOOP
  WHEN_SEVERE_ERROR:
    vmx_put_error "vmx: severe_error"
    goto END_LOOP
  WHEN_CONTROL_Y:
    control_y_string = "vmx: control_y"
    control_y_count = 1 + control_y_count
    if control_y_count .eq. 1 then -
      control_y_string = control_y_string + " (only " + -
      max_control_y + " allowed)"
    if control_y_count .eq. max_control_y - 1 then -
      control_y_string = control_y_string + " (next is last)"
    if control_y_count .eq. max_control_y then -
      control_y_string = control_y_string + " (last)"
    if control_y_count .gt. max_control_y then -
      control_y_string = control_y_string + " (exit)
    vmx_put_error control_y_string
    if control_y_count .gt. max_control_y then goto RETURN
END_LOOP: 

$! end VMX;

goto LOOP
RETURN: is_verify = f$verify(is_verify)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file VMXSHELL.COM -------- ---------- ---------- --------->
from_vmx = true
@vmx_dir:shell 'p1' 'from_vmx' 'p2'
!<-- end  ---------- ---------- ---------- ---------- <---------

(help DEBUG) DEBUG turns the trace on in the command procedures that 
implementVMX and SHELL. NO_DEBUG turns the trace off. This is useful
to locate the problem if anything goes wrong. It also helps understand
the behaviour of the command procedures.

Note that you can change the symbol DEBUG to, for instance, 
DEBUG_ON in your init file.

The argument is one of:
VMX     SHELL     PIPE     FORK
SEND    NICE      EXEC     HISTORY

Note that you can set, for instance, is_debug_vmx :== true
in your init file. (end help)
(help ECHO) ECHO echoes its arguments (up to 8) on sys$output.
ECHERR does the same on sys$error.

To echo more than 8 arguments, use \b as a blank, and \n
as a newline to separate arguments.

ECHO is most useful as the first command in a pipe, as in:

echo send \n John \n\n How are you today\b? | mail;

-- sends a short message to user John. (end help)
(help EDIX) EDIX edits the file vmx_users_dir:USER_NAME.ini, if you're
user USER_NAME. The editor used is vmx_edit, EDT by default.
If the file doesn't exit, EDIX creates it, as a copy of the
file vmx_dir:initvmx.com.
Once the file exists, it is executed every time vmx is 
invoked. It allows you to tailor the environment, including
values or symbols used by vmx.

res_extension   :== ".ext"  -- changes extension for batch log files
debux           :== @vmx_dir:debug
vmx_nice        :== submit/printer/delete/id/log_file=[]'res_extension
vmx_spawn       :== spawn/nowait/log
vmx_edit        :== edit/edt/command=MY_OWN_DIRECTORY:edtini.edt
max_count       :== 99     -- maximum number of a command
constant_ps1    :== "%"    -- changes prompt
is_help_message :== false  -- gets rid of initial message
is_vmx_wanted   :== false  -- doesn't run vmx, but allows use of SH (end help)
(help FILES) SHELL creates temporary files (removed by vmx_delete) and output
files when IO redirection is specified. VMX also creates temporary
files when a previous command is being edited for modification.

To preserve the temporary files, simply define vmx_delete :== "!"
in your init file. Note that you can modify the file extensions. 
Their meaning is as follows:

vmx_extension : used by VMX and SHELL for various files
ini_extension : used for the users init files (in vmx_users_dir)
com_extension : used for command files
job_extension : used by PIPE for batch jobs command files

in_file       : used for input redirection
out_extension : used for output redirection
err_extension : used for error redirection
res_extension : used for the result file of a batch job (end help)
(help FORK) FORK creates parallel processes, which names are USER_NAME_1,
USER_NAME_2 etc. if you're user USER_NAME.

The input and/or output of the process can be redirected, as in:
>&command_line;          <&command_line;          <>&command_line;

The process can be created by a batch job, in which case its
output must be redirected:
^>&command_line;       ^<>&command_line; 

vmx_spawn can be changed in your init_file to:
vmx_spawn :== spawn/nowait/log
so as to notify you of the creation of the process.

use CPU (:== monitor processes/topcpu) to see which processes
are currently executing.
use STOP USER_NAME_1 to kill process USER_NAME_1. (end help)
(help GRAMMAR) command_line   ::= new_command  | old_command
new_command    ::= fork_command | pipe_command | vmx_command

fork_command   ::= [^] {<, >} [&] pipe_command
pipe_command   ::= shell_command [ | pipe_command]

shell_command  ::= next_command ; next_command
next_command   ::= {<, >, ?, +} single_command [ ; shell_command]
single_command ::= "" | any VMS command

vmx_command    ::= EDIX | INIX | INYX | HS | HX | EX

old_command    ::= repeat_command | edit_command | single_command
repeat_command ::= "\" | number of a previous command
edit_command   ::= "/"  [number of a previous command] (end help)
(help HELP)

  Format:
 
    ^ command_line;                       + command;
    & command_line;                       < command;
 
    command_1 | command_2;                > command;
    command_1 ; command_2;                ? command;

    [/] [number]                          \

    HX (help)  INIX (inits count)      HS [number] (history)
    EX (exit)  INYX (inits control_y)  EDIX        (edits init_file) (end help)
(help HS) HS [number] prints the history of command lines.
By default, number is min (max_lines_for_hs, current_number)

INIX brings current_number back to init_count, 0 by default. This
also happens if current_number exceeds max_count. Otherwise you
might get the error message "Too many symbols defined"

INYX brings control_y_count back to 0. You are logged out of vmx
when you have typed more than max_control_y (3 by default) ^Ys. This
feature is there to prevent looping when vmx is recently installed.

EX is to exit vmx (the inner invocation only)
HX is the help for vmx. (end help)
(help NICE) NICE and SEND are used to send batch jobs.
NICE sends a job consisting of a single command:
+command;

To submit a command file in batch, simply use:
+@command_file;

You can change vmx_nice in your init_file, so as to be notified
of the job being sent:
vmx_nice :== submit/id/log_file=[].res

And the extension of the log file:
res_extension    :== ".log"

Use RMJOB (:== del sys$batch/entry=) to abort a job.
Use B     (:== show queue sys$batch) to see the batch queue. (end help)
(help PIPE) PIPE creates a pipe between a group of command; the operator |
has lower precedence than ^ and &, but higher precedence than ; + < > ?
Several pipes can link filters, as in this batch process:
^ <>& echo hello, world | copy | reverse | copy;
Any command can appear in a group of commands, as in:
com_1 ; com_2 | <com_3 ; >?com_4 ; com_5 | com_6 ; +com_7
Don't forget a semicolon somewhere on the line:
echo send\n John \n\n How are you? | mail; -- sends mail to John
but
echo send\n John \n\n How are you? | mail  -- no semicolon, will echo
          "send 
           John 

           How are you today ? | mail" (end help)
(help SEMANTIX)
Any command line submitted to the shell from vmx, ie any command line
containing special characters, must contain at least one semicolon ";"
The semicolon serves to recognize which command lines are to be 
interpreted by the shell.

The period "." must be used instead of the semicolon to indicate
the version number, as in file.dat.1 (end help)
(help SEND) NICE and SEND are used to send batch jobs.
SEND sends an entire command_line:
^command_line;

You can change vmx_nice in your init_file, so as to be notified
of the job being sent:
vmx_nice :== submit/id/log_file=[].res

And the extension of the log file:
res_extension    :== ".log"

Use RMJOB (:== del sys$batch/entry=) to abort a job.
Use B     (:== show queue sys$batch) to see the batch queue. (end help)
(help SH) SH is the interface to the shell, used when command_lines come
directly from a user. For instance, if you are using the VMS 
history mechanism rather than VMX, you may wish to use the shell to
interpret a complicated (but powerful) command_line.

First solution: Call VMX, wait for a prompt, enter a command_line,
and exit with EX.

Second solution: Use SH. Only one string argument, no space allowed. Use
\s as a space, except maybe for ECHO (use \b). Semicolon not required. 
An optional numeric second argument (equivalent to current_number in VMX.)
Exemple:
SH DIR/DATE
SH ECHO\sHello,\bWorld!
SH ECHO\sSEND\n\sJohn\s\n\n\sHow\sAre\sYou\sToday\b?\s|\sMail; -- a pipe

Third solution: Use VMX_SHELL (in general from a command procedure) (end help)
(help SHELL)
  SHELL is an interpreter that accepts a command_line from a user (see SH) 
or from VMX (if it contains a semicolon) and runs it according to the
special characters used (see GRAMMAR and SEMANTIX)
  Exemples of command_lines:
<& com_1 | <? com_2 ; + com_3 | >? com_4   -- a process
^ >& command_line;                         -- a batch job
  Exemples of groups of commands in pipes:
+ com_1; com_2 | <>? com_3 ; com_4 | com_5 -- double pipe
  Exemples of single commands:
+ command;                                 -- a batch job
< command;                                 -- input from temp.in
> command;                                 -- output to current_numberXX.out
? command;                                 -- errors to current_numberXX.err
  To redirect IO from/to a file, use mv (move = rename). This pipe:
23_ com_1 | com_2; 
  is equivalent (only in command number 23) to:
23_ > com_1 ; mv 23x0.out temp.in ; < com_2 (end help)
(help SYMBOL) A number of symbols are defined for use by VMX and SHELL. Note 
that you are free to redefine them if you want to obtain a
different behaviour. Examples:

out_extension : extension for output files
vmx_pipe      : the command file that implements pipes
vmx_edit      : the editor used by EDIX and by VMX to edit a command

max_control_y    : maximum number of ^Y allowed
max_count        : maximum number of commands in VMX
init_count       : number of the first command in VMX
max_lines_for_hs : number of previous commands displayed by HS

constant_ps1  : the prompt in VMX
constant_tab  : the tab used in HS
false         : a boolean  constant
is_vmx_wanted : a boolean variable, true if you want SHELL but not VMX
is_debug_vmx  : a boolean variable, changed by DEBUG and NO_DEBUG (end help)
(help TOPICS)
  Additional information available:

   DEBUG       ECHO        EDIX        FILES       FORK            GRAMMAR
   HELP        HS          NICE        PIPE        SEMANTIX        SEND
   SH          SHELL       SYMBOL      VMX         VMXSHELL        (end help)
(help VMX) VMX is a command_line interpreter with a history mechanism. It
calls the shell to interpret command_lines containing a semicolon.
The other command_lines are run directly by VMS.
VMX begins its execution by running vmx_dir:initvmx.com. Then, for
user user_name, it runs vmx_users_dir:user_name.ini (file created or
modified by EDIX.) It then loops accepting command_lines. It catches
exceptions such as ^Y, severe_error, etc. It exits on EX, has a help
HX, a history HS. 
To repeat a previous command, number 35 for instance, just type 35.
Type /35 to edit command number 35, it will be submitted when you
exit the editor with "EXIT" or ignored if you leave it with "QUIT"
Use \ to repeat the previous command, / to edit it.
VMX serves mainly as a driver for the shell. If you prefer to use the
VMS history mechanism, the shell is still available as SH. In any case,
the last line of your login.com file should be "vmx". This allows
interpretation of batch jobs when they log in. Set the boolean
is_vmx_wanted to false if necessary. (end help)
(help VMX_SHELL) VMX_SHELL is the interface to the shell used by VMX, 
or by other command procedures when it is not convenient to use SH, 
for instance, when it is not desirable to replace a space by \s.

The command procedure should include the following lines:
first_argument := """THE COMMAND LINE TO INTERPRET""" -- 3 double quotes
line_arg        = 'first_argument

It can also contain the following two lines, otherwise the current_number
used by the shell to create temporary and output files will be 0 by default:
second_argument = ANY NUMBER BETWEEN 0 and 999
count_arg      := 'second_argument

The call to shell is then of the form:
vmx_shell line_arg [count_arg]
Exemple: To avoid IO redirection to files with the same name:
  ...
vmx_shell first_command_line       1
vmx_shell second_command_line      2 (end help)

!--> file INITVMX.COM --------- ---------- ---------- --------->
vmx_extension    :== ".vmx"
ini_extension    :== ".ini"
com_extension    :== ".com"
job_extension    :== ".job"

in_file          :== "temp.in"
out_extension    :== ".out"
err_extension    :== ".err"
res_extension    :== ".res"

hx               :== @vmx_dir:help
hs               :== @vmx_dir:history
edix             :== @vmx_dir:edix
debug            :== @vmx_dir:debug
no_debug         :== @vmx_dir:nodebug

sh               :== @vmx_dir:sh
vmx_shell        :== @vmx_dir:vmxshell
vmx_send         :== @vmx_dir:send
vmx_fork         :== @vmx_dir:fork
vmx_pipe         :== @vmx_dir:pipe
vmx_exec         :== @vmx_dir:exec
vmx_delete       :== @vmx_dir:delete

vmx_nice         :== submit/noprinter/delete/noid/log_file=[]'res_extension
vmx_spawn        :== spawn/nowait/nolog
vmx_rename       :== rename
vmx_copy         :== copy
vmx_edit         :== edit/edt/command=vmx_dir:edtini.edt

vmx_put_line     :== write sys$error
vmx_put_help     :== if is_help_message    then write sys$error
vmx_put_warning  :== if is_warning_message then write sys$error
vmx_put_error    :== if is_error_message   then write sys$error

max_control_y       :== 3
max_count           :== 333
init_count          :== 0
max_lines_for_hs    :== 15

constant_ps1        :== "_"
ps1                 :== "''constant_ps1'"
constant_tab        :== "     "
tab                 :== "''constant_tab'"

false               :== 0
true                :== 1

is_help_message      :== true
is_warning_message   :== true
is_error_message     :== true
is_vmx_wanted        :== true

is_debug_vmx         :== false
is_debug_shell       :== false
is_debug_pipe        :== false
is_debug_fork        :== false
is_debug_send        :== false
is_debug_nice        :== false
is_debug_exec        :== false
is_debug_history     :== false
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file INITVMX.COM --------- ---------- ---------- --------->
vmx_extension    :== ".vmx"
ini_extension    :== ".ini"
com_extension    :== ".com"
job_extension    :== ".job"

in_file          :== "temp.in"
out_extension    :== ".out"
err_extension    :== ".err"
res_extension    :== ".res"

hx               :== @vmx_dir:help
hs               :== @vmx_dir:history
edix             :== @vmx_dir:edix
debug            :== @vmx_dir:debug
no_debug         :== @vmx_dir:nodebug

sh               :== @vmx_dir:sh
vmx_shell        :== @vmx_dir:vmxshell
vmx_send         :== @vmx_dir:send
vmx_fork         :== @vmx_dir:fork
vmx_pipe         :== @vmx_dir:pipe
vmx_exec         :== @vmx_dir:exec
vmx_delete       :== @vmx_dir:delete

vmx_nice         :== submit/noprinter/delete/noid/log_file=[]'res_extension
vmx_spawn        :== spawn/nowait/nolog
vmx_rename       :== rename
vmx_copy         :== copy
vmx_edit         :== edit/edt/command=vmx_dir:edtini.edt

vmx_put_line     :== write sys$error
vmx_put_help     :== if is_help_message    then write sys$error
vmx_put_warning  :== if is_warning_message then write sys$error
vmx_put_error    :== if is_error_message   then write sys$error

max_control_y       :== 3
max_count           :== 333
init_count          :== 0
max_lines_for_hs    :== 15

constant_ps1        :== "_"
ps1                 :== "''constant_ps1'"
constant_tab        :== "     "
tab                 :== "''constant_tab'"

false               :== 0
true                :== 1

is_help_message      :== true
is_warning_message   :== true
is_error_message     :== true
is_vmx_wanted        :== true

is_debug_vmx         :== false
is_debug_shell       :== false
is_debug_pipe        :== false
is_debug_fork        :== false
is_debug_send        :== false
is_debug_nice        :== false
is_debug_exec        :== false
is_debug_history     :== false
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file INITVMX.COM --------- ---------- ---------- --------->
vmx_extension    :== ".vmx"
ini_extension    :== ".ini"
com_extension    :== ".com"
job_extension    :== ".job"

in_file          :== "temp.in"
out_extension    :== ".out"
err_extension    :== ".err"
res_extension    :== ".res"

hx               :== @vmx_dir:help
hs               :== @vmx_dir:history
edix             :== @vmx_dir:edix
debug            :== @vmx_dir:debug
no_debug         :== @vmx_dir:nodebug

sh               :== @vmx_dir:sh
vmx_shell        :== @vmx_dir:vmxshell
vmx_send         :== @vmx_dir:send
vmx_fork         :== @vmx_dir:fork
vmx_pipe         :== @vmx_dir:pipe
vmx_exec         :== @vmx_dir:exec
vmx_delete       :== @vmx_dir:delete

vmx_nice         :== submit/noprinter/delete/noid/log_file=[]'res_extension
vmx_spawn        :== spawn/nowait/nolog
vmx_rename       :== rename
vmx_copy         :== copy
vmx_edit         :== edit/edt/command=vmx_dir:edtini.edt

vmx_put_line     :== write sys$error
vmx_put_help     :== if is_help_message    then write sys$error
vmx_put_warning  :== if is_warning_message then write sys$error
vmx_put_error    :== if is_error_message   then write sys$error

max_control_y       :== 3
max_count           :== 333
init_count          :== 0
max_lines_for_hs    :== 15

constant_ps1        :== "_"
ps1                 :== "''constant_ps1'"
constant_tab        :== "     "
tab                 :== "''constant_tab'"

false               :== 0
true                :== 1

is_help_message      :== true
is_warning_message   :== true
is_error_message     :== true
is_vmx_wanted        :== true

is_debug_vmx         :== false
is_debug_shell       :== false
is_debug_pipe        :== false
is_debug_fork        :== false
is_debug_send        :== false
is_debug_nice        :== false
is_debug_exec        :== false
is_debug_history     :== false
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file CD.COM     ---------- ---------- ---------- --------->
set def [.'p1']
sho def
!<-- end  ---------- ---------- ---------- ---------- <---------

(* COPY1.PAS *)
program copy1 (input, output);
var c : char;
begin
  while not EOF do
  begin
    while not EOLN do
    begin
      read(c);
      write('[', c, ']');
    end;
    readln;
    writeln;
  end;
end.
(* END *)
(* COPY2.PAS *)
program copy2 (input, output);
var c : char;
begin
  while not EOF do
  begin
    while not EOLN do
    begin
      read(c);
      write('{', c, '}');
    end;
    readln;
    writeln;
  end;
end.
(* END *)

!--> file CWD.COM    ---------- ---------- ---------- --------->
set def 'p1'
sho def
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file DEF0.COM   ---------- ---------- ---------- --------->
assign disk0:[root.z.vmx.bin]    vmx_dir
assign disk0:[root.z.vmx.users]  vmx_users_dir
assign disk0:[root.z.vmx.help]   vmx_help_dir
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file DEF1.COM   ---------- ---------- ---------- --------->
b        :== show queue sys$batch
q        :== show queue sys$print
p        :== print/feed/header/noflag
rmjob    :== del sys$batch/entry=
rmpri    :== del sys$print/entry=
e        :== edit/edt/command=bin:edtini.edt
pwd      :== sho def
sd       :== set def
cpu      :== monitor processes/topcpu
l        :== dir
ls       :== dir
lr       :== dir [...]
ll       :== dir/date/size
llr      :== dir/date/size [...]
llp      :== dir/date/size/prot
sv       :== set verify
sn       :== set noverify
st       :== sho term
stt      :== set term/dev=vt100
who      :== sho u
cp       :== copy
mv       :== rename
dl       :== delete
sym      :== sho sym
log      :== sho log
k        :== sho time
pro      :== sho prot
sys      :== sho syst
chmod    :== set prot/prot=(system:wred, owner:wred, group:re, world:re)
chmid    :== set prot/prot=(system:re,   owner:re,   group:re, world:re)
nomod    :== set prot/prot=(system:wred, owner:wred, group:, world:)
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file DEF2.COM   ---------- ---------- ---------- --------->
sleep   :== @bin:sleep
up      :== @bin:up
cd      :== @bin:cd
cwd     :== @bin:cwd
rmdir   :== @bin:rmdir
mkdir   :== @bin:mkdir
rm      :== @bin:rm
go      :== @bin:profile
d0      :== @bin:def0
d1      :== @bin:def1
d2      :== @bin:def2
d3      :== @bin:def3

copy_1  :== run bin:copy1
copy_2  :== run bin:copy2
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file DEF3.COM   ---------- ---------- ---------- --------->
vmx     :== @vmx_dir:vmx
sh      :== @vmx_dir:sh
echo    :== @vmx_dir:echo
echerr  :== @vmx_dir:echerr
!<-- end  ---------- ---------- ---------- ---------- <---------

set mode change

!--> file MKDIR.COM  ---------- ---------- ---------- --------->
create/dir [.'p1']
$
dir 'p1'
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file PROFILE.COM --------- ---------- ---------- --------->
set protection=(system:wred, owner:wred, group:re, world:re)/default
@bin:def0
@bin:def1
@bin:def2
@bin:def3
write sys$error "start_up done"
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file RM.COM     ---------- ---------- ---------- --------->
del 'p1'.* 
$
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file RMDIR.COM  ---------- ---------- ---------- --------->
set prot=(owner:wred) 'p1'.dir
$
del 'p1'.dir.*
$
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file SLEEP.COM  ---------- ---------- ---------- --------->
if f$locate(":", p1) .ne. f$length(p1) then wait 0:'p1'
if f$locate(":", p1) .eq. f$length(p1) then wait 0:0:'p1'
!<-- end  ---------- ---------- ---------- ---------- <---------


!--> file UP.COM     ---------- ---------- ---------- --------->
if p1 .eqs. "" then p1 = 1
index = 0
LOOP:
  index = 1 + index
  if index .gt. p1 then goto RETURN
  set def [-]
END_LOOP: goto LOOP
RETURN: sho def
!<-- end  ---------- ---------- ---------- ---------- <---------