This package contains the Rattlesnake FTP command interpreter, which reads and executes FTP command files (a.k.a. FTP command scripts).
FTP command scripts are text files containing FTP commands (statements). Commands are delimited by newlines. Commands in turn are composed of tokens (or words), which are separated by whitespace characters (spaces and tabs).
Comments begin with a hash mark (#) and end at the end of line (the (next newline). Any word beginning with a hash mark indicates the start of a comment. Examples:
get '#not#a#comment' #comment#word put not#a#comment.txt # comment
String literals are enclosed within double quotes (e.g., "foo") or single quotes (e.g., 'bar'). Strings containing embedded quote characters must specify the quotes using character escape sequences, which are $" for a double quote and $' for a single quote. So the string specified as "<He said, $"Hello!$">" represents the string <He said, "Hello!">. An embedded '$' is specified as $$, e.g., "$$50.00".
Token words can always be quoted so that they are not misinterpreted:
a+long/file*name.txt #one token 'a+long/file*name.txt' #ditto ($path/$file) #expression, five tokens '($path/$file)' #one token
Token words that begin with a '(', '<', '>', '&', or § are treated as expressions, and are split into multiple tokens.
set x1 = $path/$file.$ext #single word '$path/$file.$ext' set x2 = "$path/$file.$ext" #same as x1 set y1 = ($path/$file.$ext) #expression set y2 = ( $path / $file . $ext ) #same as y1 echo >>$path/$file.$ext $count #
Certain contexts expect a pathname, e.g.:
get dir/foo.txt my/bar.txt
Pathnames can always be quoted to insure that they are not treated as expressions:
if $v = "dir/foo.txt" then set x = $path/$Ff # expression else set x = '$path/$Ff' # string
Conversely, an expression can be provided in any context where a pathname is expected by enclosing it within parentheses:
get ('dir/' . $fname . $ext)
Vars (variables) can always be quoted to enforce their "stringiness":
"$F" "${path}/$Fn"
Parentheses are required in most contexts that expect a Term expression, which can lead to some surprises. For example:
if (:read foo.txt) ...The foo.txt looks like a well-formed filename, but since it occurs within enclosing parentheses, it is parsed as three separate tokens (foo . txt), forming the operands and operators of a concatenation expression. The result is the string 'footext', which is probably not what was intended. This kind of situation is solved by using quotes:
if (:read 'foo.txt') ...Now the operand of the :read operator is the string 'foo.txt'.
Since Term expressions can be single words, strings, or variable names, parentheses are not needed. So the following examples are all legal:
get foo.$ext if $flag put $fnameAnything more complicated that a single token, though, generally requires the presence of surrounding parentheses:
get (foo . $ext) if (not $flag) echo done
A good rule of thumb is:
When in doubt, add extra quotes or parentheses.
Pathnames on MS-DOS and MS/Windows can look like:
"c:\usr\bob\bin\foo.txt" 'bin\$Ff'whereas the same filenames look different in Unix:
"/usr/bob/bin/foo.txt" 'bin/$Ff'
The norm() function (described below) normalizes either form of the pathname into its proper native form.
A var (or variable) is a named value that can be set by the user or the FTP interpreter. Built-in vars are predefined by the interpreter, and others vars can be defined by the user. Vars can also be used to access the values of external environment variables. All vars are internally stored as character strings, but can be interpreted within expressions as either strings or as numbers (integers) depending on the context.
Var names are words composed of alphabetic letters, digits, and/or underscores. Some examples:
i name fName filename2 file_name_1
Vars are defined with the var command:
var file var count = 0 var root = /u/home/bob var path = '$root/foo.txt' var n = $count + 1
Vars that are not explicitly initialized to a value are set to an empty string ('').
Vars are set using the set command:
set i = 1 set name = 'foo.txt' set count = ($count+1) set file = ($root . / . $path . / $name . '.' $ext) set path = "$root/$path/$name.$ext" set path = (sub($file, 0, 12))
Vars are replaced with their values by prefixing their name with a $ character:
get $name $destName.txt
Var names can also be placed within braces, which may be necessary in situations where the name is followed by alphanumeric characters:
get ${name}_orig.txt ${dest}2.txt
Var replacement occurs in all contexts, even within quoted strings:
get $name.arc "${name}.tar" !tar -xvf '${name}.tar'
Var replacement is disabled by escaping the $ operator by preceding it with another $ operator:
get foo.$$tmp #Get the file named "foo.$tmp"
Var replacement is also disabled for a token word or string by the $- escape sequence, which turns off var replacement for the rest of the word or string:
get $-$foo.$tmp #Get '$foo.$tmp', var $tmp is not replaced put $dir/$-file$tmp.txt #Var $dir is replaced but $tmp is not
Var names are not case-sensitive, except for the the first character. That is, case is only important for the initial character of the var name, and is ignored for the rest of the name. So the names $file, $filE, and $fILE all designate the same var, whereas $err and $Err designate two different vars.
Built-in (predefined) var names always start with a capital (upper-case) letter or underscore, such as $F, $Error, and $_t. User-defined var names always start with a lower-case letter, such as $f and $count.
The special syntax ${$foo} refers to the value of the environment variable named foo. If there is no such environment variable, the resulting value is an empty string (''). Whether or not the name is case-sensitive depends on the underlying operating system. Note that the braces are required, since $$foo causes the $ to be escaped, resulting in the value '$foo'. Note also that environment variable values are always assumed to be string (non-numeric) values.
For example, on MS-DOS and MS/Windows systems, the PATH environment variable is set to the list of directories to search for executable programs. This variable can be examined from the FTP shell:
if (${$path} = '') { echo 'PATH is not set.' exit } echo 'PATH:' ${$path}
Stmt... # Command list : (empty) : Stmt Stmt... Stmt : BlockStmt : AppendStmt : AsciiStmt : BinaryStmt : BreakStmt : CallStmt : CdupStmt : ChdirStmt : CloseStmt : ContinueStmt : DeleteStmt : DirStmt : EchoStmt : ExitStmt : ForeachStmt : FuncDefStmt : GetStmt : GlobStmt : GotoStmt : IfStmt : LabelStmt : LocalChdirStmt : MdeleteStmt : MdirStmt : MgetStmt : MkdirStmt : MlsStmt : MputStmt : NopStmt : OpenStmt : PrintStmt : PutStmt : PwdStmt : ReadStmt : RenameStmt : RepeatStmt : ReturnStmt : RmdirStmt : SetStmt : ShellStmt : ShiftStmt : SleepStmt : TimeoutStmt : UserStmt : VarStmt : WhileStmt : SpecialStmt AppendStmt # Append (put) a file : app[end] [Session] Term [Term] nl AsciiStmt # ASCII text mode : asc[ii] [Session] nl BinaryStmt # Binary mode : bin[ary] [Session] nl BlockStmt # Command group : '{' nl Stmt... '}' nl : '{' '}' nl BreakStmt # Terminate looping : break [name] nl CallStmt # Subroutine call : FuncCallExpr nl CdupStmt # Change directory up : cdup [Session] nl ChdirStmt # Change remote directory : cd|chdir [Session] Term nl CloseStmt # Close connection : close [Session] nl : disc[onnect] [Session] nl ContinueStmt # Next loop iteration : continue [name] nl DeleteStmt # Delete file : del[ete] [Session] Term... nl DirStmt # Directory listing : dir [Session] [max Term] [Term...] nl : ls [Session] [max Term] [Term...] nl EchoStmt # Print to stdout : echo ['-n'] ['>'|'>>' Term] Term [[','] Term]... nl ExitStmt # Terminate : exit [Term] nl : quit [Term] nl : bye [Term] nl ForeachStmt # File get loop : foreach [Session] Term... [in Term] [max Term] nl Stmt FuncDefStmt # Subroutine definition : func '@'name '(' [name [',' name]...] ')' nl Stmt GetStmt # Get a file : get [Session] Term [Term] nl GotoStmt # Goto label : goto name nl IfStmt # If-then-else : if Term nl Stmt [else nl Stmt] : if Term nl Stmt [else IfStmt] LabelStmt # Command label : name: nl LocalChdirStmt # Change local directory : lcd|lchdir [Session] Term nl MkdirStmt # Create directory : mkdir [Session] Term nl NopStmt # No-op command : nop nl OpenStmt # Open connection : open [Session] Term [Term] nl PrintStmt # Print to a file : print ['>'|'>>' Term] Term [[','] Term]... nl PutStmt # Put a file : put [Session] Term [Term] nl PwdStmt # Get current dir : pwd [Session] nl ReadStmt # Read into a var : read name ['<' Term] nl RenameStmt # Rename file/dir : ren[ame] [Session] Term Term nl RepeatStmt # Repeat loop : repeat nl Stmt ReturnStmt # Subroutine return : return [Term] nl RmdirStmt # Remove directory : rmdir [Session] Term... nl SetStmt # Variable set : set name = Term nl : set name '[' Expr ']' = Term nl ShellStmt # Shell command : '!' Term... nl ShiftStmt # Shift command parms : shift [Term] nl SleepStmt # Sleep : sleep Term [Term] nl UserStmt # User ID and password : user [Session] Term [Term] nl VarStmt # Variable definition : var name ['=' Term ] nl WhileStmt # While loop : while Term nl Stmt SpecialStmt # Debugging info : '%stack' nl : '%sessions' nl : '%vars' nl : '%verbose' Term nl Session # Session number : '&' Term : '§' Term nl # End of line : newline
FTP commands are listed in alphabetical order.
BlockStmt : '{' nl [Stmt...] '}' nl : '{' '}' nl
Command block.
Executes zero or more commands as a single group. The commands comprising the
command block are enclosed within braces ({ }).
A command block is syntactically equivalent to a single command, so it
can be used in any context where a single command is expected but more than
one command is desired.
Command blocks may contain other (nested) command blocks. Each command block opens another level of scope, so any vars declared within it are accessible only to the commands or nested blocks within that block.
Example:
if ($flag) { var fname = $Fn get $fname.txt put $fname.tmp ren $fname.tmp $fname.txt foreach $fname*.html { get $Ff if ($Error != 0) { del $Ff !mv -f $Ff $Ff.done } } }
AppendStmt : app[end] [Session] Term [Term] nl
Appends a local file to a file on the remote FTP system.
The optional Session specifies which FTP session to send the file to.
If this is not specified, session &1 is assumed.
The Term-1 expression is evaluated and the resulting value specifies
the name of the local file to send.
The optional Term-2 expression specifies the name of the file to append
to on the remote system. If this is not specified, the remote name is the
same as the local name (Term-1).
This command is similar in operation to the put command, except that the local file contents are appended to the end of the remote file instead of replacing its contents entirely. File appends are non-destructive, meaning that the file is only read from the local system, and is not deleted after the append operation.
Example:
append foo.txt append $fname.$ext append (repl('foo', 'bar', $F)) '$F.new'
BreakStmt : break [name] nl
Terminate a loop.
This command stops all iterations of the currently executing loop command
(i.e., a foreach,
repeat, or
while command).
Execution then resumes at the next command following the loop.
Example:
foreach *.txt { get $Ff if ($Error != 0) break }
ChdirStmt : cd|chdir [Session] [Term] nl
Change the directory on the remote system.
This command changes the current directory on the remote system to the name
specified by Term.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
Example:
cd src chdir &2 /users/$user/docs
CdupStmt : cdup [Session] nl
Change the directory on the remote system one level up, to the parent
directory of the current directory.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
Example:
cdup chdir &2
CloseStmt : close [Session] [Term] nl : dis[connect] [Session] [Term] nl
Close the remote connection.
Logs off and disconnects the remote FTP session.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
Example:
close disconnect &2
ContinueStmt : continue [name] nl
Force the next iteration of a loop.
This command skips the rest of the commands in the body of the currently
executing loop command
(i.e., a foreach,
repeat, or
while command),
and starts the next iteration of the loop.
Example:
foreach &1 *.txt { get &1 $Ff if ($Error > 0) continue put &2 $Ff }
DeleteStmt : del[ete] [Session] Term... nl
Deletes (removes) one or more files on the remote FTP system.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
Each Term expression is evaluated and the resulting value specifies
the name of a remote file to delete.
Example:
del foo.txt bar.txt del $fname.$ext delete (repl('foo', 'bar', $F))
DirStmt : dir|ls ['&' Term] [max Term] [Term...] nl
List the directory contents of the remote FTP system.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
Each Term expression is evaluated and the resulting value specifies
a filename pattern to list on the remote system.
The optional max clause specifies the maximum number of files to
list.
Example:
dir *.txt ls &2 max 100
EchoStmt : echo ['-n'] ['>'|'>>' Term] Term [[','] Term]... nl
+INCOMPLETE
See also: print.
ExitStmt : exit|bye|quit [Term] nl
+INCOMPLETE
ForeachStmt : foreach [Session] Term... [in Term-2] [max Term-3] nl Stmt
Loops over a list of matching filenames.
One or more Term expressions specify the filename patterns to search
for on the remote system.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
The optional in Term-2 operand specifies the directory to
search. If this is not specified, the current remote directory is assumed.
The optional max Term-3 operand specifies the maximum number
of filenames to find. If this is zero or not specified, no maximum is assumed
and all matching filenames on the remote system are found.
Due to the way the FTP protocol is implemented, the in expression is not limited to being a subdirectory name, but can be any valid command option accepted by the remote dir or ls command. For example, most Unix systems can be passed an argument of '-tr', which causes the list of files to be sorted in reverse order according to their modification date.
As a special case, a Session expression that evaluates to zero (&0) indicates that the local system is to be searched for matching filenames.
Once all of the matching filenames have been found, the body of the loop (Stmt) is executed, once for each filename. Upon entering the loop body, the predefined vars $F, $Fd, etc. are set to the components of the next matching filename.
The break and continue commands can be used to modify the looping. A continue command skips the rest of the commands in the body of the loop and begins the next iteration of the loop. A break command stops the looping completely and causes execution to resume at the next command following the loop. A goto command can be used to transfer execution out of the loop body entirely.
Example:
# Find all files on the remote system foreach * echo $F # Retrieve all text files from the 'docs' directory foreach &2 *.txt *.html in docs max 100 { get &2 docs/$Ff $Ff del &2 docs/$Ff } # Delete all temp files in the current local directory foreach &0 *.tmp !del "$Ff"
GetStmt : get [Session] Term-1 [Term-2] nl
Retrieves a file from the remote FTP system.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
The Term-1 expression is evaluated and the resulting value specifies
the name of the remote file to retrieve.
The optional Term-2 expression specifies the name to give the file on
the local system. If this is not specified, the local name is the same as the
remote name (Term-1).
File retrievals are non-destructive, meaning that the file is only read from the remote system, and is not deleted after the get operation.
Example:
get foo.txt get &2 $fname.$ext get (repl('foo', 'bar', $F)) '$F.new'
GotoStmt : goto name nl
Transfer control to a label.
This command causes the specified command label to be the next command that
will be executed. If the label is outside the block containing the
goto command (e.g., if the goto command is within a nested
block or loop command), the execution of the current block is terminated.
Any vars defined within the block go out of scope and no longer exist.
Example:
foreach *.txt { get $Ff if ($Error != 0) goto Error put $Ff } Error: echo Get failed for: $Ff, error: $Error
IfStmt : if Term nl Stmt-1 [else nl Stmt-2] : if Term nl Stmt-1 [else IfStmt-2]
Conditional execution command.
Evaluates the Term expression, and if the resulting value is true,
Stmt-1 (the then command) is executed, otherwise the command
following the else is executed.
The else clause is optional. If the then clause comprises
more than one command, it must be enclosed within block braces
({ }).
Example:
# Simple if-then command if $stop { echo Terminating exit } # Chained if-then-else commands if (:read 'foo.txt') put foo.txt else if (:read 'bar.txt') put bar.txt else echo File not found
See also: while.
LabelStmt : name: nl
Command label.
The name specifies a name for the command. This is useful for naming a
target command for the goto, break, and continue
commands. A label name is like a variable name but is suffixed by a colon
(:). A label can also be a keyword followed by a colon (e.g.,
quit:), but this practice is discouraged.
A label is a command all by itself, and does not need to be followed by another command. It only serves as a named location, and does not perform any action (similar to the the nop command). One or more labels may precede a non-label command, each of which provides a different name for that command.
Labels are not case sensitive, so that label:, Label:, and LABEL: all refer to the same command label name. (It is recommended that the same capitalization style be used for all labels within a command script for readability.) Labels must be unique within the block in which they appear. Label names within a nested block may hide other labels with the same name defined in outer blocks.
Example:
Loop: foreach *.txt { get $Ff if (not $Error = 0) goto Error put $Ff continue Loop Error: echo Failure for: $Ff, file skipped }
LocalChdirStmt : lcd|lchdir [Session] [Term] nl
Change the directory on the local system.
This command changes the current directory on the local system to the name
specified by Term.
The optional Session specifies which FTP session the command affects.
This means that each session has its own notion of the local directory
separate from all other sessions, which affects where local files are located
by the get,
put, and
foreach commands.
If a session is not specified, session &1 is assumed.
Note that unlike the cd command, session &0 may be specified
(even though it has no associated remote directory).
Example:
lcd src lchdir &2 /users/$user/docs
NopStmt : nop nl
No-op command.
Does not do anything. This command is useful in contexts expecting a command
but no action needs to be performed.
Example:
if (len($fname) = 0) nop else if ($fname ~ *.tmp) nop else get $fname
See also: block command.
PrintStmt : print ['>'|'>>' Term] Term [[','] Term]... nl
+INCOMPLETE
See also: echo.
PutStmt : put [Session] Term [Term] nl
Stores a local file to the remote FTP system.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
The Term-1 expression is evaluated and the resulting value specifies
the name of the local file to send.
The optional Term-2 expression specifies the name to give the file on
the remote system. If this is not specified, the remote name is the same as
the local name (Term-1).
File sends are non-destructive, meaning that the file is only read from the local system, and is not deleted after the put operation.
Example:
put foo.txt put &2 $fname.$ext put (repl('foo', 'bar', $F)) '$F.new'
RepeatStmt : repeat nl Stmt
Looping command.
Loops repeatedly, executing the Stmt command comprising the body of
the loop. Since this is effectively an infinite loop, the
break command must be used to terminate
the loop.
The break and continue commands can be used to modify the looping. A continue command skips the rest of the commands in the body of the loop and begins the next iteration of the loop. A break command stops the looping completely and causes execution to resume at the next command following the loop. A goto command can be used to transfer execution out of the loop body entirely.
Example:
# Wait until file 'foo.txt' is created repeat { if (:exists 'foo.txt') break sleep 1000 } # Retrieve a log file periodically repeat { get logfile.txt if ($Error != 0) break sleep (20*60*1000) }
RmdirStmt : rmdir [Session] Term... nl
Deletes (removes) one or more directories on the remote FTP system.
The optional Session specifies which FTP session the command affects.
If this is not specified, session &1 is assumed.
Each Term expression is evaluated and the resulting value specifies
the name of a remote directory to delete.
Example:
rmdir foo bar rmdir $dirname rmdir (repl('foo', 'bar', $Fd))
SetStmt : set name = Term nl : set name '[' Expr ']' = Term nl
Sets the value of a var (variable).
Evaluates the Term expression and changes the value of the var named
name to be the resulting value.
The name refers to the var within the most local block scope. If a var of that name is not defined within the local block, the next outer block is searched, and then the next outermost enclosing block, and so on until a matching var name is found. If name does not designate a var defined in any accessible enclosing scope, the interpreter will issue an error when the command script is compiled. Predefined (built-in) vars are defined within the outermost block.
The Term expression can refer to any var accessible within the local scope, including the var that is being modified by the set command. All expressions result in string values, even if they involve arithmetic operations.
Array vars may be set by following the var name with a bracketed subscript expression. Array indices are not limited to numeric values, and may be any string value, including empty strings (''). In this respect, arrays are more properly called hash tables. Thus a[1], a[xyz], and a['x${user}y'] are all valid elements of array a.
Note that since array indices are strings and not numbers, the exact value of the subscript expression is used to locate an element to modify in the specified hash table. This means that a[1] and a[01] designate two different elements, because 1 and 01 are two different strings (words). To limit array indexing to only numeric values, prefix the subscript expressions with a '+' unary operator. This forces both a[+1] and a[+01] to refer to the same element, which is a['1'].
Note also that an unsubscripted var name designates a var that is separate from the array of the same name, e.g., abc is a var separate and distinct from element abc[$i] (for any value of $i).
Note also that only one-dimensional arrays are supported. So a[$i] designates a valid array element, but a[$i][$j] is a syntax error. Multi-dimensional arrays can be simulated, however, by concatenating one or more array indices together to form a single unique array index. For example, a[$i.';'.$j] joins index $i and $j together to produce a single index into array a.
Example:
set fname = foo.txt set newfile = '$oldfile.new' set count = ($count+1) set out = ($path . '/' . repl('.in' '.out' $infile)) set a[1] = /bin/file1.txt set key['foo'] = 'bar' set fname[$idx[$i+1]] = ('/bin/' . ($i+1)) set table['$i&$j'] = (100*$i + $j) set path = ${$path}
See also: var, subscript operator.
VarStmt : var name ['=' Term ] nl
Defines a var (variable).
Adds a new var named name to the local scope.
If Term is specified, the expression is evaluated and the var is
initialized with the resulting value. Otherwise, the var is initialized to an
empty string (''), which is logically equal to false and
numerically equal to zero.
Var definitions within a block can hide other var definitions in outer scopes (which will cause a warning to be issued by the interpreter when the command script is compiled). Predefined (built-in) vars cannot be redefined or hidden. A given var name can be defined only once within a block scope.
Once control passes out of a block, all vars defined within that block go out of scope and no longer exist.
Example:
var fname var count = 1 var newfile = '$oldfile.new' var out = ($path . '/' . repl('.in' '.out' $infile)) var cwd = ${$cd}
See also: set.
WhileStmt : while Term nl Stmt
Conditional loop command.
Loops repeatedly, executing the Stmt command comprising the body of
the loop. Before each iteration, the conditional Term expression is
evaluated, and if the resulting value is true, the body command is executed.
The loop iterations continue until the conditional expression is false.
A conditional expression of true can be specified, effectively
producing an infinite loop (equivalent to a
repeat command).
If the loop body comprises more than one command, it must be enclosed within
block braces ({ }).
The break and continue commands can be used to modify the looping. A continue command skips the rest of the commands in the body of the loop and begins the next iteration of the loop. A break command stops the looping completely and causes execution to resume at the next command following the loop. A goto command can be used to transfer execution out of the loop body entirely.
Example:
while $keep_going call @getNextFile() while true { get $fname if (not :exists $fname) break } while ($count <= $max) { get $fname.$count.txt set count = ($count+1) }
One problem encountered with this syntax is the classic "dangling else" problem. Consider the following commands:
if $e1 if $e2 stmt1 else stmt2As shown, the else command is matched up with the second if command, so the commands are actually interpreted as:
if $e1 { if $e2 stmt1 else stmt2 }Appropriately placed braces (command block) solve this problem:
if $e1 { if $e2 stmt1 } else stmt2
Term : true : false : Var : number : string : word : '(' Expr ')' ExprList : Expr : Expr ',' ExprList Expr : IfExpr IfExpr : OrExpr : OrExpr '?' IfExpr ':' IfExpr # Conditional OrExpr : AndExpr : AndExpr or Expr # Logical or AndExpr : NotExpr : NotExpr and AndExpr # Logical and NotExpr : RelExpr : not NotExpr # Logical not RelExpr : TestExpr : TestExpr '=' TestExpr # Comparison : TestExpr '!=' TestExpr : TestExpr '<' TestExpr : TestExpr '<=' TestExpr : TestExpr '>' TestExpr : TestExpr '>=' TestExpr : TestExpr '~' TestExpr # Pattern match : TestExpr '!~' TestExpr TestExpr : StringExpr : ':dir' StringExpr # Is directory : ':exists' StringExpr # Exists : ':file' StringExpr # Is file : ':mtime' StringExpr # Modification time : ':read' StringExpr # Can read : ':size' StringExpr # Size : ':write' StringExpr # Can write StringExpr : AddExpr : AddExpr '.' StringExpr AddExpr : MulExpr : MulExpr '+' AddExpr # Add : MulExpr '-' AddExpr # Subtract MulExpr : UnaryExpr : UnaryExpr '*' MulExpr # Multiply : UnaryExpr '×' MulExpr # Multiply : UnaryExpr '/' MulExpr # Divide : UnaryExpr '÷' MulExpr # Divide : UnaryExpr '%' MulExpr # Modulo UnaryExpr : CallExpr : '+' UnaryExpr # Numeric : '-' UnaryExpr # Numeric negative CallExpr : BuiltinExpr : FuncCallExpr : SubscriptExpr SubscriptExpr : Term : Var : Var '[' Expr ']' BuiltinExpr : format '(' Expr ',' Expr ')' : index '(' Expr ',' Expr ')' : len '(' Expr ')' : norm '(' Expr ')' : repl '(' Expr ',' Expr ',' Expr ')' : rindex '(' Expr ',' Expr ')' : sub '(' Expr ',' Expr [',' Expr] ')' : trim '(' Expr ')' FuncCallExpr : '@'name ['(' [ExprList] ')'] # Subroutine call Var : '$'name # Variable : '${'name'}' # Variable : '${$'name'}' # Environment variable
Operator precedences, from lowest to highest, are:
There are also prefix operators, which introduce a term within a command but otherwise do not act like actual operators:
Operators are listed in order from lowest precedence to highest.
IfExpr : Expr-1 '?' Expr-2 ':' Expr-3
Conditional logical expression (if-then-else).
If the Expr-1 operand evaluates to true, then Expr-2 (the "then"
operand) is evaluated and its result is returned, otherwise Expr-3 (the
"else" operand) is evaluated and its result is returned.
Note that the '?' and ':' operators generally should be surrounded by spaces in order to allow the parser to distinguish them from pathnames and filename patterns. For example, a?b:c resembles a pattern, whereas a ? b : c is an expression and cannot be confused with a pattern or filename.
Example:
set blk = ($n<0 ? $size : $size/2)
OrExpr : Expr-1 or Expr-2
Logical disjunction (or).
Returns true if either Expr-1 or Expr-2 evaluate to true.
Note that if the left operand is true, the right operand is not evaluated
(this is called short-circuit evaluation).
This operator returns a logical true (which is equal to 1) or false (which is equal to 0) value.
Example:
set x = ($n < 0 or $n > $max) if ($rate = 0 or $cost/$rate <= 0) echo Indeterminate: $cost/$rate
AndExpr : Expr-1 and Expr-2
Logical conjunction (and).
Returns true if both Expr-1 and Expr-2 evaluate to true.
Note that if the left operand is false, the right operand is not evaluated
(this is called short-circuit evaluation).
This operator returns a logical true (which is equal to 1) or false (which is equal to 0) value.
Example:
set x = ($n >= 0 and $n <= $max) if ($rate != 0 and $cost/$rate > 0) echo Result: $cost/$rate = ($cost/$rate)
NotExpr : not Expr
Logical negation (not).
Returns true if Expr evaluates to false, and vice versa.
In other words, the result is the logical inverse of Expr.
This operator returns a logical true (which is equal to 1) or false (which is equal to 0) value.
Example:
set x = (not $c < 0) if (not :mtime 'foo.txt' > $lastTime) echo File has not aged: 'foo.txt'
RelExpr : Expr-1 '=' Expr-2 # Equal to : Expr-1 '!=' Expr-2 # Not equal to : Expr-1 '<' Expr-2 # Less than : Expr-1 '<=' Expr-2 # Less than or equal to : Expr-1 '>' Expr-2 # Greater than : Expr-1 '>=' Expr-2 # Greater than or equal to : Expr-1 '~' Expr-2 # Pattern match : Expr-1 '!~' Expr-2 # Pattern mismatch
Relational/comparison operators.
Compares the value of Expr-1 to the value of Expr-2.
The =, !=, <, <=, >,
and >= operators test for equality, inequality,
less than, less than or equal to,
greater than, and greater than or equal to, respectively.
If both operands are numeric values, the comparison is an arithmetic
operation, comparing the numeric values of the operands. Otherwise it is a
string operation, comparing the character string values of the operands.
Note that the != operator can be used as an exclusive-or operator:
if (($size < 0) != ($max < 0)) { # Either $size or $max is negative, but not both echo satisfied conditions }
Pattern match operators.
The ~ operator treats the right operand as a
filename pattern,
and returns true if the left operand matches the pattern.
The !~ operator operates similarly, but returns the opposite result
(thus x !~ y is equivalent to
not x ~ y).
All of the relational operators return a logical true (which is equal to 1) or false (which is equal to 0) value.
Example:
set flag = ($fname >= 'foo000.txt') # String compare set stop = (+$count = 0) # Numeric compare set flag = (len($user) <= 8) set flag = ($fname ~ '[0-9]*[a-z].[a-z]*[a-z0-9]')
TestExpr : ':dir' StringExpr # Is directory : ':exists' StringExpr # Exists : ':file' StringExpr # Is file : ':mtime' StringExpr # Modification time : ':read' StringExpr # Can read : ':size' StringExpr # Size : ':write' StringExpr # Can write
File test predicates.
Evaluates Expr and uses the resulting value as a filename, applying
a test to the local file that it names. The :mtime operator returns
the time that the file was last modified (in the same format as var
$Time), and the :size operator returns the numeric length
of the file (in bytes).
The other operators return a logical true if the file meets the conditions of
the predicate operator, and false otherwise.
If the named file does not exist on the local system, the numeric operators
(:mtime and :size) return zero and the boolean operators
return false.
Example:
# Wait for a file to be deleted while (:exists $fname) sleep 1000 # Send a file only if it's not empty if (:size $fname > 0) put $fname
StringExpr : Expr-1 '.' Expr-2
The . operator concatenates (joins together) the string values of Expr-1 and Expr-2, resulting in a new string value.
Due to the lexical constraints of the command interpreter, the '.' character may appear as either an operator or as a filename character, depending on the context in which it appears, which can lead to some surprising results:
set file = foo set ext = txt get $file.$ext # => get 'foo.txt' get '$file.$ext' # => get 'foo.txt' get ($file.$ext) # => get 'footxt' if (:read $file.$ext) ... # => if (:read 'footxt') if (:read '$file.$ext') ... # => if (:read 'foo.txt')
Remember that outside parentheses or within quotes, a '.' character is just a character, but within parentheses it is an operator. When in doubt, use quotes or parentheses to make the intended meaning clear to the parser.
The concatenation operator can be used to convert a numeric value into a string value, so that the resulting value can be used in a string comparison expression:
set n = 123 if ($n > '0999') ... # Numeric compare: 123 > 999 (false) if ($n . '' > '0999') ... # String compare: '123' > '0999' (true) echo $n # Prints string: 0123 echo ($n."") # Prints string: 0123 echo (+$n) # Prints number: 123
Example:
set fullname = ($path . '/' . $name . '.' . $ext)
AddExpr : Expr-1 '+' Expr-2 : Expr-1 '-' Expr-2
Addition operators.
Evaluates Expr-1 and Expr-2 and adds or subtracts the resulting
numeric values together, producing a numeric result.
The + operator can be used to convert a string value into a numeric value:
set str = 00123 # = '00123' set num = (+$str) # = 123 set xyz = +0847.50 # = '+0847.50' set num = (+$xyz) # = 847 (integer) if ($str > 99) ... # String compare: '00123' > '99' (false) if (+$str > 99) ... # Numeric compare: 123 > 99 (true)
Example:
set size = ($size + $incr)
Term : '(' Expr ')'
Parenthetical grouping.
Returns the result of Expr. Parentheses can be used to group
subexpressions so that they are evaluated in the intended order instead of in
the default precedence order.
Note that parentheses are required for any Term expression involving more than a single operand.
Example:
set x = ($n >= 0 and ($n <= $max or $err))
SubscriptExpr : Term : Var : Var '[' Expr ']'
Array subscript (index).
An array expression (e.g., $abc) followed by a bracketed expression
specifies an array indexing operation, treating the var as an array and using
the value of the expression as in index into the array.
Array indices are not limited to numeric values, and may be any string value,
including empty strings (''). In this respect, arrays are more
properly called hash tables. Thus $a[1], $a[xyz],
and $a['x${user}y'] are all valid elements of array $a.
Note that since array indices are strings and not numbers, the exact value of the subscript expression is used to locate an element in the specified hash table. This means that $a[1] and $a[01] are two different elements, because 1 and 01 are two different strings (words). To limit array indexing to only numeric values, prefix the subscript expressions with a '+' unary operator. This forces both $a[+1] and $a[+01] to refer to the same element, which is $a['1'].
Note also that an unsubscripted var name designates a var that is separate from the array of the same name, e.g., $abc is a var separate and distinct from element $abc[$i] (for any value of $i).
Note also that only one-dimensional arrays are supported. So $a[$i] designates a valid array element, but $a[$i][$j] is a syntax error. Multi-dimensional arrays can be simulated, however, by concatenating one or more array indices together to form a single unique array index. For example, $a[$i.@.$j] joins index $i and $j together to produce a single index into array $a.
Due to limitations in the current version of the command script parser, array subscripts can only occur within parenthesized expressions (terms), and cannot appear within string literals. Thus ($arr[$i+1]) is a valid term, but '$arr[$i+1]' is not; the latter is interpreted like the expression ('$arr'.'[$i+1]'). It is recommended that temporary vars be used to get around this limitation.
See also: set.
Term : string : word : Var : number : true : false : '(' Expr ')'
The lowest level component of any expression is a term, which acts as a single operand within the expression.
A term is a string literal (e.g., 'Hello.txt'), a number literal (e.g., 42), a word token (e.g., file, bin/$app/foo.dat, and c:\$app\bin\foo.exe), a var name (e.g., $Ff and ${count}), or the keyword true or false.
String tokens are sequences of characters enclosed within single quotes (') or double quotes ("). They can contain almost any character, including spaces and multinational characters. For example, 'path/docs/$file.txt', "Hello, world", 'Resumé.doc', 'Años', and "schlußel.txt". Strings may also include character escape sequences, e.g., 'my$$acnt' and 'don`'t know'.
Word tokens are composed of just about any sequence of characters, including multinational characters, but not spaces or leading parentheses. For example: file, c:\bin\file.exe, $Error, and $count#xyz($n). Words may also include character escape sequences, e.g., my$$acnt and don`'t`sknow.
Number literals are actually just a special cases of word tokens, being composed of decimal digits (0 thru 9) and an optional leading sign (+ or -). For example: 0, -1, +42. Only integers are supported, so words such as 3.141592 and -12.50 are not numbers.
The true keyword evaluates to true within logical expressions and to 1 in numeric or string contexts. The false keyword evaluates to false within logical expressions and to 0 in numeric or string contexts.
A term operand may also be any expression enclosed within parentheses. Note that the presence of the opening parenthesis '(' causes the words within the parentheses to be split into individual tokens. This means that $path/$file.txt is a single word token, but ($path/$file.txt) is seven separate tokens forming an expression: ( $path / $file . txt ).
The built-in (predefined) funcs are:
The following is a list of predefined (built-in) vars (variables). The examples shown below assume a filename of "/path/dir/file.ext" and a date of <Fri 2006-05-02 09:14:36.075>.
+INCOMPLETE
Several of the commands and expressions make use of filename patterns (or just patterns for short). These are a special kind of regular expression for matching filenames. Empty patterns ("") are not allowed. Patterns consist of one or more of the following:
Pattern | Description |
---|---|
. | Matches a period (.) in a filename. |
? | Matches any single filename character. |
* | Matches zero or more filename characters. |
[abc] [a-z] |
Matches a single filename character matching any of the characters within the character set inside the brackets. The form [a-z] is used to specify a character range, which matches any filename character between a and z inclusive. |
[^abc] [^a-z] |
Matches a single filename character that does not match any of the characters within the brackets. |
other | Any ordinary printable character, which matches that exact character within a filename. |
Note that some file systems support case-sensitive filenames, so alphabetic characters must match exactly, i.e., the pattern character q matches only a q within a filename and not a Q. On the other hand, other file systems support case-insensitive filenames, treating upper and lower case alphabetic characters the same, i.e., the pattern character q matches both a q and a Q within a filename. The same rules also apply to character sets and ranges.
Example Applications
Listing 1
# Retrieve remote ZIP files periodically and extract their contents var cnt = 0 user xxx yyy repeat { # Get a list of zipfiles sorted by date if ($cnt = 0) open ftp.host set cnt = 0 foreach *.zip in '-t' max 100 { # Retrieve a zipfile set Error = 0 get $Ff if ($Error != 0) continue # Skip the file set cnt = ($cnt + 1) # Extract the contents of the zipfile !jar -xf "$Ff" !del "$Ff" } # Wait if there are no zipfiles to get yet if ($cnt = 0) { close sleep (20 * 60*1000) # 20 minutes } }Listing 2
# Move files from one remote system to another user &1 xxx1 yyy1 user &2 xxx2 yyy2 repeat { # Connect to the remote systems repeat { set Error = 0 open &1 ftp.host1 if ($Error = 0) { set Error = 0 open &2 ftp.host2 if ($Error = 0) break close &1 } # Can't connect to both systems, wait and try again sleep (30*1000) # 30 sec } # Move files from one system to the other repeat { # Get a list of files to transfer foreach &1 * in '-t' max 100 { # Get a file from the 1st system set Error = 0 get &1 $Ff if ($Error != 0) continue del &1 $Ff # Copy the file to the 2nd system set Error = 0 put &2 $Ff $Ff.tmp if ($Error != 0) continue ren &2 $Ff.tmp $Ff !del $Ff } # Sleep if there were no files transferred if ($F = '') break } # Close the FTP connections and wait for a while close &1 close &2 sleep (10 * 60*1000) # 10 min }