Table of Contents

Anatomy of Your LOGIN.COM Script

Your LOGIN.COM command file (or script) may be one of the most important files you'll ever use and maintain on a VMS system. It is stored in your own “home directory” (logical name SYS$LOGIN), and your version or instance of LOGIN.COM is yours alone, unique to you and your needs.

Your LOGIN.COM script is also a “work-in-progress”… you will likely find reasons to modify and update it frequently over the years (especially if you're reading these PARSEC Wiki Articles out of curiosity or a desire to learn more), and as such, it's “never done!” It is:

Maintaining Your LOGIN.COM

You maintain (update, change, evolve) your LOGIN.COM with any standard VMS text editor, such as EVE, EDT, Emacs, or whatever you like to use:

$ SHOW DEFAULT
DSA2:[LRICKER]
$ ! equivalent to SYS$LOGIN for this user...
$ SHOW LOG SYS$LOGIN /FULL
   "SYS$LOGIN" [exec] = "DSA2:[LRICKER]" (LNM$JOB_859C3940)
$
$ EDIT /TPU LOGIN.COM

  ... edit window/buffer appears, editing occurs ...

Don't forget to save your work (changes) frequently – each editing file SAVE or WRITE operation creates a new version of your LOGIN.COM file, so you can “always go back” if something doesn't turn out right. PURGE this file if and whenever necessary.

Structure of LOGIN.COM (template, skeleton)

When you first got your own VMS login account (username, home directory, etc.), your VMS system administrator likely “seeded” a site-specific stock copy of a LOGIN.COM command file in your home directory when s/he created it. That “seed file” may have been well organized and thought-out… or maybe it wasn't.

Let's look at a trivial, empty LOGIN.COM script, a sort of “skeleton” for the various parts that a well-organized LOGIN.COM script should look like:

$ ! LOGIN.COM -- "My" login script -- template/skeleton version   'F$VERIFY(0)'
$ !
$ GOTO 'F$MODE()'                                      !  3
$ !
$ ! ==========                                         !  5
$INTERACTIVE:                                          !  6
$ SET TERMINAL /INQUIRE                                !  7
$ !
$ ! Interactive login process definitions go here...   !  9
$ !
$ EXIT    ! 'F$VERIFY(0)'                              ! 11
$ !
$ ! ==========                                         ! 13
$BATCH:                                                ! 14
$ !
$ ! Batch job definitions, if any, go here...          ! 16
$ ! (Why don't we do a SET TERMINAL command here?)     ! 17
$ !
$ EXIT    ! 'F$VERIFY(0)'                              ! 19
$ !
$ ! ==========                                         ! 21
$NETWORK:                                              ! 22
$OTHER:                                                ! 23
$ ! This section is rarely, if ever, used...           ! 24
$ !
$ EXIT    ! 'F$VERIFY(0)'                              ! 26
$ !

The non-empty (comment) lines are numbered above. After the first (line #1) comment, internal-documentation line, here's how these lines work or function in this skeleton script:

The above is nothing more than a skeleton, a template, for a real LOGIN.COM file. It has the advantage of being well-structured and simple to understand, therefore simple to maintain and extend into your future. Other forms and contents of the file are possible, and you may see those other formats. Unfortunately, the contents of many LOGIN.COM files that we've seen “in the wild” are unstructured, often a tangled mass of:

$ IF F$MODE() .EQS. "INTERACTIVE" THEN do-something
...
$ IF F$MODE() .EQS. "BATCH" THEN do-something-else
...
$ IF F$MODE() .EQS. "INTERACTIVE" THEN declare-something-elsse
$ IF F$MODE() .NES. "BATCH" THEN GOTO some-other-lable
...

…Well, you get the idea. Spaghetti code is not much fun to extend or maintain. We advocate and teach the skeleton-template form above.

First Enhancement to LOGIN Script

Here's a possible first pass which extends the template LOGIN.COM file, maybe suitable for an “ordinary, normal user” of VMS:

$ ! LOGIN.COM -- "My" login script -- 1st version                  'F$VERIFY(0)'
$ !
$ ! Likely want this set the same way for all process types:
$ SET PROTECTION=(S:RWED,O:RWED,G,W) /DEFAULT
$ !
$ GOTO 'F$MODE()'
$ !
$ ! ==========
$INTERACTIVE:
$ SET TERMINAL /INQUIRE /INSERT
$ !
$ CALL CreDir "SYS$SCRATCH"  ! separate sub-dir for temp-files   ! ***
$ CALL CreDir "LOGS"         ! a sub-dir for batch log-files     ! ***
$ !
$ ! VMS-style command aliases (symbols) --                       ! ***
$ dir      == "DIRECTORY /SIZE /DATE /PROTECTION"                ! ***
$ move     == "RENAME"                                           ! ***
$ prlj     == "PRINT /QUEUE=LASERJET /LOG"                       ! ***
$ ssys*tem == "PIPE SHOW SYSTEM | SEARCH SYS$PIPE "              ! ***
$ SUBM*IT  == "SUBMIT /NOTIFY /NOPRINT /LOG_FILE=logs:"          ! ***
$ count    == "PIPE SHOW SYSTEM | SEARCH SYS$PIPE /STATISTICS "  ! ***
$ ! ...                                                          ! ***
$ !
$ SET PROMPT="''F$DIRECTORY()'$ "                                ! ***
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$ ! ==========
$BATCH:
$ !
$ ! Batch job definitions, if any, go here...
$ ! (Why don't we do a SET TERMINAL command here?)
$ !
$ ! Replicate this interactive symbol, for consistency
$ !   in self-SUBMITted batch jobs --
$ SUBM*IT  == "SUBMIT /NOTIFY /NOPRINT /LOG_FILE=logs:"          ! ***
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$ ! ==========
$NETWORK:
$OTHER:
$ ! This section is rarely, if ever, used...
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$CreDir: SUBROUTINE
$ ! P1 : Subdirectory to test and create
$ ! P2 : Job logical name to define
$ IF ( P2 .EQS. "" ) THEN P2 = P1
$ homedd = F$TRNLNM("SYS$LOGIN") - "]"
$ IF ( F$SEARCH("''P1'.DIR;1") .EQS. "" )
$ THEN CREATE /DIRECTORY [.'P1']
$ ENDIF
$ DEFINE /JOB /NOLOG 'P2' 'homedd'.'P1']
$ EXIT 1
$ ENDSUBROUTINE  ! CreDir
$ !

The comments ! *** mark the lines where are added to replace INTERACTIVE stanza Line 9 in the original template/skeleton, plus a line added to the BATCH stanza to make re-submits easier.

This also includes a DCL subroutine, labelled CreDir (for “create directory”) at the end of this script, to test for a user's home directory subdirectory, conditionally creating it plus a suitable logical name if it doesn't already exist. Here, two subdirectories are tested and created, one for SYS$SCRATCH (redirected from the user's home directory), and a LOGS subdirectory for batch log-files.

Note that these enhancement lines were easy to add right into the appropriate INTERACTIVE or BATCH stanza, and didn't take any complicated IF/THEN/ELSE logic to make things work right. It's always best to keep things simple…

Enhancements for a Power User

Here's a version for a VMS “power user”:

$ ! LOGIN.COM -- "My" login script -- 2nd version                  'F$VERIFY(0)'
$ !
$ ! Likely want this set the same way for all process types:
$ SET PROTECTION=(S:RWED,O:RWED,G,W) /DEFAULT
$ !
$ GOTO 'F$MODE()'
$ !
$ ! ==========
$INTERACTIVE:
$ SET TERMINAL /INQUIRE /INSERT
$ SET CONTROL=(Y,T)
$ !
$ CALL CreDir "SYS$SCRATCH"
$ CALL CreDir "LOGS"
$ CALL CreDir "COM"
$ !
$ EveInitF = "sys$login:eve$init.eve"
$ IF ( F$SEARCH(EveInitF) .EQS. "" )
$ THEN CREATE 'EveInitF'  ! a *nix-style "here-doc":
! EVE$INIT.EVE -- sets EDT-keypad and bound-cursor modes:
SET KEYPAD EDT
SET CURSOR BOUND
$ ! -- end of "here-doc" data
$ ENDIF
$ DEFINE /PROCESS /NOLOG eve$init 'EveInitF'
$ !
$ ! VMS-style command aliases (symbols) --
$ dir      == "DIRECTORY /SIZE /DATE /PROTECTION"
$ ed*it    == "EDIT /TPU /INIT=eve$init"
$ move     == "RENAME"
$ prlj     == "PRINT /QUEUE=LASERJET /LOG"
$ ssys*tem == "PIPE SHOW SYSTEM | SEARCH SYS$PIPE "
$ SUBM*IT  == "SUBMIT /NOTIFY /NOPRINT /LOG_FILE=logs:"
$ count    == "PIPE SHOW SYSTEM | SEARCH SYS$PIPE /STATISTICS "
$ !
$ ! Linux/Unix-style command aliases (symbols) --
$ cd       == "SET DEFAULT"
$ pwd      == "SHOW DEFAULT"
$ cls      == "TYPE /PAGE=CLEAR_SCREEN NLA0:"
$ cp       == "COPY"
$ rm       == "DELETE"
$ mv       == "RENAME"
$ ls       == "DIRECTORY /SIZE /DATE /PROTECTION"
$ home     == "PIPE SET DEFAULT sys$login ; SHOW DEFAULT"
$ upt*ime  == "SHOW SYSTEM /NOPROCESS /FULL"
$ !
$ PgSize  = F$INTEGER(F$GETDVI("TT","TT_PAGE")) - 2    ! allow some margin
$ PgWidth = F$INTEGER(F$GETDVI("TT","DEVBUFSIZ")) - 2
$ tail     == "TYPE /TAIL=''PgSize'"
$ ! ...
$ !
$ nodename = F$EDIT( F$GETSYI("NODENAME"), "TRIM,UPCASE" )
$ SET PROMPT="''nodename'$ "
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$ ! ==========
$BATCH:
$ !
$ ! Batch job definitions, if any, go here...
$ ! (Why don't we do a SET TERMINAL command here?)
$ !
$ ! Replicate this interactive symbol, for consistency
$ !   in self-SUBMITted batch jobs --
$ SUBM*IT  == "SUBMIT /NOTIFY /NOPRINT /LOG_FILE=logs:"
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$ ! ==========
$NETWORK:
$OTHER:
$ ! This section is rarely, if ever, used...
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$CreDir: SUBROUTINE
$ ! P1 : Subdirectory to test and create
$ ! P2 : Job logical name to define
$ IF ( P2 .EQS. "" ) THEN P2 = P1
$ homedd = F$TRNLNM("SYS$LOGIN") - "]"
$ IF ( F$SEARCH("''P1'.DIR;1") .EQS. "" )
$ THEN CREATE /DIRECTORY [.'P1']
$ ENDIF
$ DEFINE /JOB /NOLOG 'P2' 'homedd'.'P1']
$ EXIT 1
$ ENDSUBROUTINE  ! CreDir
$ !

The CreDir subroutine is called for two more (suggested) subdirectories.

Sys-Admin's LOGIN script

Here's a version with command-tools for a VMS system administrator:

$ ! LOGIN.COM -- "My" login script -- 3rd version                  'F$VERIFY(0)'
$ !              with System Administrator tools
$ !
$ ! Likely want this set the same way for all process types:
$ SET PROTECTION=(S:RWED,O:RWED,G,W) /DEFAULT
$ !
$ GOTO 'F$MODE()'
$ !
$ ! ==========
$INTERACTIVE:
$ SET TERMINAL /INQUIRE /INSERT
$ SET CONTROL=(Y,T)
$ !
$ CALL CreDir "SYS$SCRATCH"
$ CALL CreDir "LOGS"
$ CALL CreDir "COM"
$ !
$ EveInitF = "sys$login:eve$init.eve"
$ IF ( F$SEARCH(EveInitF) .EQS. "" )
$ THEN CREATE 'EveInitF'  ! a *nix-style "here-doc":
! EVE$INIT.EVE -- sets EDT-keypad and bound-cursor modes:
SET KEYPAD EDT
SET CURSOR BOUND
$ ! -- end of "here-doc" data
$ ENDIF
$ DEFINE /PROCESS /NOLOG eve$init 'EveInitF'
$ !
$ ! VMS-style command aliases (symbols) --
$ IF F$GETSYI("ARCH_NAME") .NES. "VAX"
$ THEN dir == "DIRECTORY /ACL /SIZE /DATE /PROT /WIDTH=(FILENAME=24,SIZE=10)"  !Alpha, Itanium
$ ELSE dir == "DIRECTORY /ACL /SIZE /DATE /PROT /WIDTH=SIZE=7"
$ ENDIF
$ ed*it    == "EDIT /TPU /INIT=eve$init"
$ move     == "RENAME"
$ prlj     == "PRINT /QUEUE=LASERJET /LOG"
$ ssys*tem == "PIPE SHOW SYSTEM | SEARCH SYS$PIPE "
$ SUBM*IT  == "SUBMIT /NOTIFY /NOPRINT /LOG_FILE=logs:"
$ count    == "PIPE SHOW SYSTEM | SEARCH SYS$PIPE /STATISTICS "
$ !
$ ! Linux/Unix-style command aliases (symbols) --
$ cd       == "SET DEFAULT"
$ pwd      == "SHOW DEFAULT"
$ cls      == "TYPE /PAGE=CLEAR_SCREEN NLA0:"
$ !           See also [...BEGINNER]ANSISEQ.COM for a better approach to clr-screen
$ cp       == "COPY"
$ rm       == "DELETE"
$ mv       == "RENAME"
$ ls       == "DIRECTORY /SIZE /DATE /PROTECTION"
$ home     == "PIPE SET DEFAULT sys$login ; SHOW DEFAULT"
$ upt*ime  == "PIPE SHOW SYSTEM /NOPROCESS | SEARCH /HIGHLIGHT=UNDERLINE sys$pipe uptime"
$ !
$ PgSize  = F$INTEGER(F$GETDVI("TT","TT_PAGE")) - 2    ! allow some margin
$ PgWidth = F$INTEGER(F$GETDVI("TT","DEVBUFSIZ")) - 2
$ tail     == "TYPE /TAIL=''PgSize'"
$ !
$ ! Pick up TCP/IP command set (if HP-stack):
$ tcpipdef = "sys$manager:tcpip$define_commands.com"
$ IF ( F$SEARCH(tcpipdef) .NES. "" ) THEN @'tcpipdef'
$ ! (If your TCP/IP stack is MultiNet or TCPware, then the above doesn't apply,
$ !  and your TCP-commands will be defined differently, see your doc-set.)
$ !
$ ! System Administrator tools --
$ anim*age    == "ANALYZE /IMAGE /SELECT=(ARCH,IMAGE_TYPE,IDENT=IMAGE,NAME)"
$ break*in    == "SHOW INTRUSION /TYPE=ALL"
$ delbreak*in == "DELETE /INTRUSION_RECORD"
$ chks*um     == "CHECKSUM /ALGORITHM=MD5 /SHOW=ALL"
$ crc         == "CHECKSUM /ALGORITHM=CRC /SHOW=ALL"
$ disk*s      == "SHOW DEVICE /MOUNTED D"
$ dheader     == "DUMP /HEADER /BLOCK=COUNT=0"
$ sclu*ster   == "SHOW CLUSTER /CONTINUOUS"
$ IF F$SEARCH("sys$login:show_cluster$init.ini") .NES. ""
$ THEN DEFINE /PROCESS /SUPERVISOR /NOLOG show_cluster$init sys$login:show_cluster$init.ini
$ ENDIF
$ !
$ privcomf = "com:privilege.com"  ! (from Lorin's repository)
$ IF ( F$SEARCH( privcomf ) .NES. "" )
$ THEN priv   == "@''privcomf'"
$      pow*er == "@''privcomf'" - ".COM" - " ONE$SHOT" + " ONE$SHOT"
$      sudo   == "''power'"   ! make a Linux-synonym too...
$ ENDIF
$ !
$ IF F$TYPE(authorize) .EQS. "" THEN auth*orize == "$SYS$SYSTEM:AUTHORIZE"
$ IF F$TYPE(sysgen)    .EQS. "" THEN sysgen     == "$SYS$SYSTEM:SYSGEN"
$ IF F$TYPE(sysman)    .EQS. "" THEN sysman     == "$SYS$SYSTEM:SYSMAN SET ENV/CLU"
$ IF F$TYPE(lancp)     .EQS. "" THEN lancp      == "$SYS$SYSTEM:LANCP"
$ IF F$TYPE(ncp)       .EQS. "" THEN ncp        == "$SYS$SYSTEM:NCP"
$ IF F$TYPE(ncl)       .EQS. "" THEN ncl        == "$SYS$SYSTEM:NCL"
$ !
$ nodename = F$EDIT( F$GETSYI("NODENAME"), "TRIM,UPCASE" )
$ SET PROMPT="''nodename'$ "
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$ ! ==========
$BATCH:
$ !
$ ! Batch job definitions, if any, go here...
$ ! (Why don't we do a SET TERMINAL command here?)
$ !
$ ! Replicate this interactive symbol, for consistency
$ !   in self-SUBMITted batch jobs --
$ SUBM*IT  == "SUBMIT /NOTIFY /NOPRINT /LOG_FILE=logs:"
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$ ! ==========
$NETWORK:
$OTHER:
$ ! This section is rarely, if ever, used...
$ !
$ EXIT    ! 'F$VERIFY(0)'
$ !
$CreDir: SUBROUTINE
$ ! P1 : Subdirectory to test and create
$ ! P2 : Job logical name to define
$ IF ( P2 .EQS. "" ) THEN P2 = P1
$ homedd = F$TRNLNM("SYS$LOGIN") - "]"
$ IF ( F$SEARCH("''P1'.DIR;1") .EQS. "" )
$ THEN CREATE /DIRECTORY [.'P1']
$ ENDIF
$ DEFINE /JOB /NOLOG 'P2' 'homedd'.'P1']
$ EXIT 1
$ ENDSUBROUTINE  ! CreDir
$ !

This final sample just adds more command aliases suitable for a system manager's needs.

Hopefully, these samples will give you some good ideas for your own LOGIN.COM file.