The general outline of a Pikt script is:
<script name>
init
status active|inactive
level emergency|urgent|critical|error|
warning|notice|info|debug
task "<script description>"
input proc|file|logfile "<process|file|logfile|string>"
["<bakfile>"]
filter "<process>"
seps "<separators>"
dat <var> <spec>
...
keys <var> [<var> ...]
begin
<statement>
...
rule
<statement>
...
...
end
<statement>
...
Of the script components listed above, just about everything is optional. For alarm scripts (appearing in .alt files and registered in alarms.cfg), what is essential is a script name (in the left-most column), followed optionally by various combinations of (indented) script sections. Here are the possibilities:
name
name begin
name end
name begin end
name init begin
name init end
name init begin end
name init rules
name init begin rules
name init rules end
name init begin rules end
For standalone scripts (appearing as .pkt files and registered in programs.cfg), the only essentials are programs statements. See the Standalone Scripts section following for more details.
As an example of just about the most bare-bones Pikt script possible, here is the obligatory "Hello World" script:
Birth begin output "Hello, world!"
Or, if you prefer, and using a more sensible and readable layout:
Death
end
output "Goodbye, cruel world!"
When run from the command line, either script simply outputs the indicated text to stdout.
Note that both scripts comply with the requirement that the script name appear in the left-most column, with the subsequent script material either following on the same line or in subsequent indented lines. Pikt is not in general sensitive to script layout.
Script components will be described in greater detail below. Viewing sample scripts will also give you a better feel for what is and is not acceptable. (Look, too, at the test validation scripts for some occasionally far-fetched examples.)
After the alarm/script identifier, an init section usually follows. In the init section, you prepare the groundwork for subsequent script actions.
An alarm is either active or inactive; if inactive, it is bypassed. (Although it might seem that this field is useless, that is not the case. An administrator of a machine might not have access to the master control machine and might, for whatever reason, need to deactivate one or more alarm scripts on a client machine. Changing "active" to "inactive"--on the client machine--achieves this. Also, we anticipate adding other status levels--"pending," say--for alarm scripts in some future PIKT version.)
The alarm is given one of eight severity levels, analogous to syslog's severity levels. The severity levels are:
-
emergency
-
urgent
-
critical
-
error
-
warning
-
notice
-
info
-
debug
Note that "urgent" is used in place of syslog's "alert". You may also use the abbreviations "emerg", "crit", "err", and "warn".
In alert messages, alarms are sorted by severity levels, from "emergency" (highest) to "debug" (lowest). The idea here is to have more important alarm output appear toward the beginning of alert messages, and less important alarm output appear toward the end.
Having said that, severity level designations are rather arbitrary. Interpret the meaning of "urgent", or what makes one alarm "critical" while another one merely a "warning", as you wish.
If for some reason you prefer that the alarm order you specify in the alerts.cfg listing determines the order of alarm output, you may override the default by adding this directive in PIKT.conf:
sort_messages NO [or: FALSE, OFF, ...;
defaults to YES, ...]
"task" is a short description of the alarm's purpose. Note that, although the task description may contain PIKT macros, it may not contain (although this, too, might change in some future PIKT version) any function calls or other variable Pikt elements. Note, too, that you might need to backslash escape certain Pikt special characters (like "@", "#", "$", "%", "=", etc.) within the task description string.
When a script is run from the command line, status, level and task are pointless, therefore optional. When run by piktd with the intent to output to an e-mail and/or printer report, a script lacking status, level and task generates an error. In general, it is best always to include the status, level, and task description.
The primary alarm input is either the output of a process, the full contents of a text file, or logfile updates (new info since the previous alarm run). If a process, it can be any system process (including multiple processes tied together by pipes) yielding text output. Pikt does not deal with binary input.
If you add a second file path to 'input file' statements, as in
input file "/etc/passwd" "/etc/passwd.bak"
this signals the pikt script interpreter to open a temporary file, in the example case /etc/passwd.pikttmp, and write each input line to that temporary file--unless you have set the special reserved variable $outlin (or $outline or $outputline) for the current input line, in which case $outlin is written to the temporary file instead of $inlin.
If a script were reviewing the passwd file and encountered some problem for a particular line, a missing password for example, you could do this
if $password eq ""
set $outlin = "$username:*:$uid:$gid:$gecos:$homedir:$shell"
fi
to lock out the account automatically.
To exclude a line, use
set $outlin = $nil()
If you fail to specify a backup file in the 'input file' statement, $outlin is treated like any ordinary string variable.
When the input file is fully processed (at the end of the script, actually), /etc/passwd is backed up to /etc/passwd.bak (in the example), and the temporary file is moved in place of /etc/passwd--but only if one or more 'set $outlin' statements actually changed anything. The rewritten input file will have the same ownerships and permissions as the old. If no changes are indicated (or if there is some problem making the backup, for example a badly specified file path), the input file is left completely unchanged. (Note that without the backup file specification and any 'set $outlin' statements, input files are also left completely unchanged.)
If you specify 'input "<string>"', this is equivalent to 'input proc "echo <string>"'.
Before script processing, input is passed through an optional filter. In a proc statement, processes can pass output to a filter through pipes, making a separate filter statement unnecessary. filter statements are really intended for use with files and logfiles.
Input is processed, line by line. Before processing, an input line is broken apart, with each part assigned to a variable name.
One or more dat statements map input data to variables. The dat ("data", too, is legal) statement takes one of three forms. The ordinal form is:
dat <var> [x]
where <var> is assigned to the data field in the x position. By default, data fields are separated by one or more spaces and/or tabs. You override this with the seps statement. "<separators>" is a string of one or more characters (including possibly the default space and tab in addition to other characters).
In ordinal dat statements, $ signifies the last field, $-1 signifies the next-to-last field, $-2 the field before that, and so on.
You may combine field specifiers of the form 1, 2, ..., $, $-1, $-2 in the same set of data specifications.
Square brackets ("[" and "]")around the ordinal field number say to strip any leading and/or trailing whitespace characters. The brackets are optional.
The second form of the dat statement specifies columnar data:
dat <var> [x,y]
This says to assign <var> to data found in columns x through y. In a series of such statements, column positions may not overlap, but don't have to be contiguous (i.e., there may be gaps).
Here, too, the optional square brackets say to strip leading/trailing whitespace--even if legitimate white space is found in the indicated columns. (Whitespace internal to the data string is not stripped.)
The third and final form of the dat statement employs a regular expression:
dat "<regexp>"
Note that <var>'s are not specified. Rather, the special variable $0 captures the portion of the input line matching the entire regular expression. $1 captures the first part of the <regexp> enclosed by parentheses (if any), $2 captures the second parenthesized subexpression, and so on. You may reference these regexp values also by $[0], $[1], ..., especially within for loops (e.g., $[#i]). (See the Regular Expressions section of this Reference.)
If no dat statement is given, any or all of the following by default refer to the entire input line: $0, $[0], $inlin, $inline, $inputline. (Pikt provides synonyms for many of the language keywords, in particular, abbreviated versions of the longer keywords).
Even if you specify numbered or columnar variables in dat statements, you can still access all parsed data by means of $0 ($[0]), $1 ($[1]), ...
Concluding the init section, the optional keys line lists variables used as database lookup keys when referring to history values (values stored from previous script runs). (See the History Logging section of this Reference.)
Next come action statements, grouped in begin, end, and rule sections.
The heart of a Pikt script is the main processing loop: A line of input (from a proc, file, or logfile) is read in, then acted upon, the next line is read in and acted upon, and so on until the input is exhausted. Before input processing, you might have a begin section, for example to initialize some variables or to take some other preliminary actions. After input processing, you might add an end section, for actions taken at the conclusion of input processing. (You can also achieve additional data processing loops within a Pikt script using a combination of #fopen()/#popen(), #read(), and #fclose()/#pclose().)
In other words:
begin [optional]
<statement>
<statement>
...
[while there's another input line] [optional]
rule
<statement>
<statement>
...
rule
<statement>
<statement>
...
...
[endwhile]
end [optional]
<statement>
<statement>
...
Note that begin, rule, and end sections are individually optional, but the script must have at least one. That is, you might have a script with only a begin section, or only an end section, or only one or more rule sections, or some combination of the three. Another requirement is that begin must precede rules, and end must follow begin and rules. Here are the possibilities:
begin
end
begin
end
rules
begin
rules
rules
end
begin
rules
end
The input processing loop comprises one or more rule sections. Strictly speaking, there is never a need to break up the set of input processing statements into separate rule sections, but doing so helps clarify program logic. (A rule line is also a handy place to put comments.) A rule section, then, usually groups together program statements pertaining to a single thing, just one attribute of the current input line.
For standalone scripts (appearing as .pkt files and registered in programs.cfg), such scripts must begin with a comment line giving the path to the pikt script interpreter:
#!/usr/local/pikt/bin/pikt
or as it might appear, with a PIKT macro, in programs.cfg:
#!=pikt
Following that, the script must have one or more Pikt statements, perhaps mixed with blank lines or comment lines.
Here is a sample Pikt script to send a SIGHUP signal to inetd as the script would appear in programs.cfg:
SigHupInetd // restart inetd
path "=prgdir/SigHupInetd.pkt"
mode 750 uid 0 gid 1
#!=pikt
# send a SIGHUP signal to inetd
init
input proc # but what about args to inetd?
#if solaris
"=ps -e -o pid,comm | =grep inetd"
#elif linux | freebsd | openbsd
"=ps -xc | =grep inetd | =grep -v grep"
#elif hpux | irix | aix
"=ps -e | =awk '{print $1 " " $4}' | =grep inetd"
#endif
dat $inetdpid 1 # 'pid' is a reserved var name
dat $inetdproc $ # unused
rule
=exec "=kill -HUP $inetdpid"
After installing this on the PIKT slave systems with
# piktc -iv +P SigHupInetd +H ...
you would have the SigHupInetd.pkt script in the PIKT prgdir:
#!/usr/local/pikt/bin/pikt
# send a SIGHUP signal to inetd
init
input proc # but what about args to inetd?
"/usr/bin/ps -e -o pid,comm | /usr/bin/grep inetd"
dat $inetdpid 1 # 'pid' is a reserved var name
dat $inetdproc $ # unused
rule
exec "/usr/bin/kill -HUP $inetdpid"
(Note that you can edit Pikt .pkt script files directly without registering them in programs.cfg or installing them from the PIKT master.)
You can directly execute this with
# SigHupInetd.pkt [assumes that the PIKT prgdir is in your path]
(be sure to 'chmod +x' the script file first) or via piktc with
# piktc -x +C "=prgdir/SigHupInetd.pkt" +H ...
Note several differences with Pikt alarm scripts (appearing within .alt files and registered in alarms.cfg): (a) the script name appears nowhere in the script (rather, it is in the script file name, in this case, SigHupInetd.pkt); (b) on the client, there is no special indentation (the script must still follow the usual indentation rules in the master programs.cfg); (c) you may embed #-style comments within the script (#-style comments are not supported within alarms.cfg alarm scripts); (d) the piktc preprocessor will honor your line and whitespace layout (and not rewrite the layout as it would when installing alarms.cfg alarm scripts as .alt files).
To repeat: #-style comments are reserved for standalone, directly executable Pikt scripts maintained within programs.cfg (or perhaps edited directly) and installed as .pkt files. #-style comments are illegal in alarm scripts maintained within alarms.cfg and installed in .alt files. (For alarm scripts, just use //-style and /* */ comments instead.)
For #-style comments, there might be instances where the parser interprets #foo-style numerical variables or #foo()-style numerical functions as comments, or vice-versa. In such instances, the fix is simply to add whitespace after the # or perhaps tweak the line layout. (Admittedly, the parser is not perfected yet. Try not to get too carried away with weird commenting and script layouts.) In general, it's just good practice to add whitespace after '#' for all comments.
Because Pikt scripts might reference history values in .hst files in the PIKT hstdir, and because Pikt scripts write to .log files in the PIKT logdir, it is imperative that the script interpreter be able to find the =piktdir. Unfortunately, at least several versions of Linux and AIX (and other OSes?) strip directory paths from the script interpreter (i.e., "#!/pikt/bin/pikt" becomes simply "#!pikt", and we have been unable to find a way to convey path information to the pikt script interpreter in a reliable or straightforward way. There are at least two fixes for this problem: (a) add the PIKT bindir (e.g., /usr/local/pikt/bin) to root's program PATH; (b) specify homdir in PIKT.conf (so long as your PIKT etcdir is one of the standard locations (/etc, /etc/pikt, /usr/local/etc, /usr/local/etc/pikt).
You can use command-line arguments with standalone Pikt scripts and reference their value within the script as $ARGV[]. #ARGV is the number of command-line arguments.
For example, if you do this
# foo.pkt 1 bar "this is a test"
or
# pikt +S foo.pkt 1 bar "this is a test"
$ARGV[0] is the script name, foo.pkt; $ARGV[1] is "1" (a text string, not the number 1); $ARGV[2] is "bar"; $ARGV[3] is "this is a test"; and #ARGC is 3 (a number, not a string). For scripts without arguments, #ARGC is zero.
Note that you cannot pass arguments to alarm scripts (in *.alt files), and $ARGV[] and #ARGC are undefined in that context.
At a lower level of detail, a Pikt script consists of a sequence of objects:
OBJECT EXAMPLES
keyword rule, if, endif, set, mail, exec
string "foo", "^[^:]+:([^:]+):", ""
number 3, -3, 3.1416, 0
operator +, |, ., -e, ==, &&, !, (, )
variable #x, $a, #y[3], $b[5], $4, %x, @x, PASSWD
function #max(#x,#y), $repeat("-",80), #false()
In general, every object serves a semantic purpose. Hence, and for example, parentheses are not required around an if condition, or the arguments to a for statement. Nor are semicolons or end-of-lines required to signal the end of a program statement.
Strings must begin and end with a double quote. Double quotes inside double-quoted strings are forbidden. (This is changed from earlier versions of PIKT.) If you need to embed a double quote inside a string, use $char(34) and the concatenation operator (.) instead. In certain circumstances, you might be able to embed double quotes within single-quoted strings and vice versa. You may also embed variables and functions within strings.
Variable names must begin with a letter, followed by any number of letters digits and underscores ('_'). Variable names are (practically speaking) unlimited in length. Case is significant. (Note: Beginning with PIKT 1.14.0, whitespace between an array name and the opening left bracket is illegal.)
Pikt has three basic data types:
TYPE EXAMPLES
strings "foo", $foo, $left("foo",1)
numbers 2, #foo, #value("2")
filehandles FOO
(prochandles)
So, "$" signifies the string type, "#" signifies the number type, and all-caps signifies the filehandle type. (prochandles are treated like filehandles.) Arithmetic may not be performed on filehandles (or prochandles).
Pikt supports both "associative" (string-indexed) and numeric (numerically-indexed) arrays. (Note: Beginning with PIKT 1.14.0, whitespace between the array name and the opening left bracket is illegal.)
Pikt associative arrays are designated by using a string expression as the array index, for example:
set $shell[$1] = $7
set #shellcnt["/bin/csh"] += 1
if #uid[$user] == 0
The string expression may be a simple string, a string function, or any other string expression (e.g., a concatenation).
There are four associative array functions, #keys(), #skeys() (also #sortkeys()), #rkeys() (also #reversekeys()), and #members(), and one special associative array keyword, unset (with its alias, delete).
#keys() maps associative array keys in such a way that you can reference each successive key like so:
for $u in #keys($shell)
... $shell[$u] ...
endfor
or, equivalently:
foreach #keys($u, $shell)
... $shell[$u] ...
endforeach
Internally, pikt maps associative array keys to a special key numeric array with a special array index, so that the previous two examples are equivalent to
for #i_u=1 #i_u<=#keys($shell) #i_u+=1
... $shell[$u[#i_u]] ...
endfor
Under most circumstances, you would use either of the first two for loop forms only.
#skeys() (aka #sortkeys()) is like #keys() except that the array keys are sorted before mapping. #rkeys() (aka #reversekeys()) is like #skeys() except that the array keys are reverse sorted.
#members() is like #keys() except that it applies to an ad hoc list of array keys specified right in the #members() function:
for $u in #members("root", "daemon", "bin", "sys")
... $shell[$u] ...
endfor
or, equivalently:
foreach #members($u, "root", "daemon", "bin", "sys")
... $shell[$u] ...
endforeach
The 'unset' keyword (aka 'delete') removes an element from an associative array, for example:
unset $shell["daemon"]
or, equivalently:
delete $shell["daemon"]
If you are tallying instance counts using associative arrays, you must be careful not to assume that uninitialized variables default to 0. Instead, you should either do something like this
if ! #defined(#a[$d])
set #a[$d] = 1
else
set #a[$d] += 1
fi
or, better, do this
=incr(#a[$d])
where the =incr() macro is defined (in macros.cfg) as
incr(N) if ! #defined((N))
set (N) = 1
else
set (N) += 1
fi
Pikt supports multi-dimensional numerically indexed arrays of from one to three dimensions. You can write, for example, things like
set $foo[#innum(),5] = $5
and
set #bar[#x,#y,#z] = #fly[123]
Note that, unlike many other languages, array indices start at 1, not 0. In Pikt, generally speaking, all indexing (in whatever context) begins with 1. (The exception is that the timing parameters in alerts.cfg follow the usual cron conventions.) Array indices are any valid numerical expression (hence are computable). Array indices may go as high as 2147483647, which should be more than enough for anybody, or for any purpose.
There is code in place, but not activated, to take numeric arrays to higher than three dimensions, but three dimensions should be more than enough under most circumstances. In unusual situations, you might kludge together four and more dimensions in an associative array with something like
$foo["$text(ndx1) $text(ndx2) $text(ndx3) $text(ndx4) ..."]
but you would probably be better off using another language or rethinking your approach to the problem.
Variables come in three different time forms:
TIME FORM EXAMPLES
current $foo, #foo
preceding @foo [during the preceding input line]
previous %foo [during the previous script run]
"$" and "#" as variable prefixes refer to current values. "@" signals the value for this variable when the preceding input line was being processed. "%" signals the value for this variable during the previous script run. (File handles never take a preceding or previous form.)
So, in Pikt, there is no need to save input data values from one line to the next. Values from the previous input loop are stored automatically for you.
@inlin (or @inline or @inputline) recalls the entire previous input line. As a special case of @, @rdlin (or @rdline or @readline) likewise recalls the entire previous read-in line in a while #read() loop.
The same is true with so-called "history variables." Pikt stores values in a data file for recall the next time the alarm script runs. If a value is tied to a particular input data variable (specified in a dat statement) and a particular line of input, Pikt does a keyword lookup (specified in a keys statement) to find the appropriate data value. (See the History Logging section of this Reference.)
String and arithmetic operators may be applied to preceding and previous variables (as well as current string and numeric variables, of course)--i.e., you may use them freely within expressions, functions, etc.--but their value may not be changed (except internally by Pikt itself).
Even when used in their preceding and/or previous forms, variables still retain their underlying string or numerical data types.
Pikt provides the usual operators, and a few not so usual:
LOGICAL DESCRIPTION
&& logical AND
|| logical OR
! logical NOT
( ) logical grouping
(Note that for config file #if-#endif preprocessing, you use the & and | set operators, not && or ||.)
The && and || operators use short-circuiting evaluation.
ARITHMETIC DESCRIPTION
+ add
- subtract
* multiply
/ divide
% modulus
** exponentiate
& bit AND
| bit OR
^ bit EXOR
~ bit COMP
<< bit left shift
>> bit right shift
== equals
> greater than
< less than
>= greater than or equal to
<= less than or equal to
!= not equal to
<> not equal to
!> not greater than
!< not less than
!>= not greater than or equal to
!<= not less than or equal to
Note the absence of the auto-increment and auto-decrement operators. Instead, you would use += and -= (see below).
The last four operators above are a bit unusual, and note that != and <> are interchangeable.
STRING DESCRIPTION
. concatenate
eq is identical to
ne is not identical to
lt precedes in dictionary order
gt follows in dictionary order
le precedes in dictionary order,
or is identical to
ge follows in dictionary order,
or is identical to
Aside from concatenation, there are many more things one can do with strings using the built-in string functions.
ASSIGNMENT DESCRIPTION
= assign to variable, used with all data
types (string, number, filehandle)
+= add and assign
-= subtract and assign
*= multiply and assign
/= divide and assign
%= modulus and assign
**= exponentiate and assign
&= bit AND and assign
|= bit OR and assign
^= bit EXOR and assign
<<= bit left shift and assign
>>= bit right shift and assign
.= concatenate and assign
FILE DESCRIPTION
-e exists
-z is zero length
-f is a regular file
-d is a directory
-c is a character special file
-b is a block special file
-p is a pipe
-l is a symlink
-S is a socket
(proc tests, similar in format to file tests, are planned for a future PIKT version.)
These operators follow the usual associativity and precedence rules.
So, too, with how objects and operators combine to form expressions. The usual rules apply (no real surprises here).
Pikt offers a wide variety of built-in functions. (More will be developed in the future as the need arises.) An unusual feature of Pikt functions is that they are data-typed: their return value is signified by either the "$" (for string) or "#" (for number) prefix. (Functions never return a preceding or previous value, so no functions are of the type @foo() or %foo().) (Note: Beginning with PIKT 1.14.0, whitespace between the function name and the opening left parenthesis is illegal.)
Note that many of the functions have synonymous names, in some cases abbreviations, in other cases names by which they are known in other popular programming languages.
In the function descriptions below, "x" signifies any expression that evaluates to either TRUE (non-zero) or FALSE (zero); "a" any string expression; "n" any numerical expression; and "F" a filehandle.
First, the number functions (functions that return a numerical value):
#abs(n)
#absval(n)
#absolutevalue(n)
returns the absolute value of n
#age(a1,a2,a3)
#fileage(a1,a2,a3)
returns the age in days since a1/a2/a3, whether a1 is a
full month name or its three-letter equivalent, and
whether a3 is a four-digit year (yyyy) or a time (hh:mm)
(a2 must be a date)
#and(x1,x2,...)
returns TRUE if expressions x1, x2, ... are all TRUE
(non-zero); FALSE otherwise (if one or more expressions
are FALSE or zero)
#ascii()
[see #code()]
#avg(n1,n2,...)
#average(n1,n2,...)
#mean(n1,n2,...)
returns the average (mean) of the values n1, n2, ...
#ceiling(n)
returns n rounded up to the nearest integer
#code(a)
#ascii(a)
#ord(a)
returns the ASCII value of the first char in a
#datevalue(n1,n2,n3)
returns the time value for the date n3/n2/n1 (dd/mm/yyyy)
#day()
returns the current calendar day (1-31)
#day(n)
returns the calendar day (1-31) of the datevalue n
#daynumber()
returns the current weekday (1-7)
#daynumber(a)
returns the weekday (1-7) for the string a ("Sunday",
"Monday", ...; or their three-letter abbreviations,
"Sun", "Mon", ...)
#daysbetween(n1,n2)
returns the number of days between the datevalues
n1 and n2
#defined(x)
#defined(a)
#defined(n)
for x, a, or n; returns TRUE (1) if x resolves to a value,
or in the case of variables has appeared in a previous dat
or set statement, or has a previous or preceding value;
FALSE (0) otherwise; #defined(ERR), #defined(NIL),
#defined("<<ERR>>"), and #defined("<<NIL>>") are
always FALSE (0)
#eoi()
#endofinput()
returns TRUE if there is no input, if at the last input
line, or if in the end section; FALSE otherwise
#err()
returns ERR
#error(a)
#error(n)
returns TRUE (1), if evaluating a or n produces an error;
FALSE (0) otherwise
#even(n)
returns TRUE, if n is even; FALSE, if n is odd
#false()
returns FALSE (0)
#fclose(F)
closes file F; returns TRUE if successful, ERR if
unsuccessful
#fileage()
[see #age()]
#fileino(a)
for path a, returns its inode
#filelinks(a)
for path a, returns its link count
#fileuid(a)
for path a, returns the uid of its owner
#filegid(a)
for path a, returns the gid of its group owner
#fileatime(a)
for path a, returns its atime (as a time value)
#filectime(a)
for path a, returns its ctime (as a time value)
#filemtime(a)
for path a, returns its mtime (as a time value)
#filesize(a)
for path a, returns its size (in bytes)
#find()
[see #search()]
#fopen(F,a1,a2)
opens file a1 with mode a2 (any of "r", "w", or "a");
returns TRUE if successful, ERR if unsuccessful
#grgid(a)
#grgid(n)
#groupgid(a)
#groupgid(n)
for group name a, or gid n, returns its gid
(as recorded in the system group file)
#hour()
returns the current hour (0-23)
#hour(n)
returns the hour portion (0-23)
of the datevalue/timevalue n
#if(x,n1,n2)
returns n1, if x is TRUE; else n2, if x is FALSE
#if(x,n)
returns n, if x is TRUE; else the special numerical
value NIL, if x is false
#index()
[see #search()]
#inlen()
#inputlength()
returns the length (number of chars) of the current
input line; 0 if outside the input processing loop
#innum()
#inputnumber()
returns the line number of the current input line;
0 if still in the begin section; the line number of
the last input line if in the end section
#int(n)
#integer(n)
returns n rounded down to the nearest integer
#isalnum(a)
returns TRUE, if the first char in a is alphanumeric
#isalpha(a)
returns TRUE, if the first char in a is a letter
#iscntrl(a)
returns TRUE, if the first char in a is a control char
#isdigit(a)
returns TRUE, if the first char in a is a digit
#isgraph(a)
returns TRUE, if the first char in a is any printing
char, except space
#islower(a)
returns TRUE, if the first char in a is a lower-case
letter
#isprint(a)
returns TRUE, if the first char in a is any printing
char, including space
#ispunct(a)
returns TRUE, if the first char in a is any printing
char but neither a space nor alphanumeric
#isspace(a)
returns TRUE, if the first char in a is a whitespace
char
#isupper(a)
returns TRUE, if the first char in a is an upper-case
letter
#keys(a)
returns the number of keys in the associative array
a; maps those keys to a string var when used as
'for <stringvar> in #keys(a)'; the keys are sequenced
in the order encountered during the script run
#keys(a1,a2)
returns the number of keys in the associative array
a2; maps those keys to string var a1 when used as
'foreach #keys(a1, a2)'; the keys are sequenced
in the order encountered during the script run
#length(a)
returns the length (number of chars) of string a
#level()
returns the current alarm's level, 0 through 7
(representing EMERG, ALERT, CRIT, ERR, WARNING,
NOTICE, INFO, DEBUG)
#max(n1,n2,...)
#maximum(n1,n2,...)
returns the maximum value among n1, n2, ...
#mean()
[see #average()]
#median(n1,n2,...)
returns the median value among n1, n2, ...
#members(a2,a3,a4,...)
like #keys() (which see), except that returns the
number of associative array keys in the sequence
a2, a3, a4, ...; maps those keys to a string var
when used as 'for <stringvar> in #members(a2, a3,
a4, ...); the keys are sequenced in the order
specified in the #members() function
#members(a1,a2,a3,a4,...)
like #keys() (which see), except that returns the
number of associative array keys in the sequence
a2, a3, a4, ...; maps those keys to a1 when used as
'foreach #members(a1, a2, a3, a4, ...); the keys are
sequenced in the order specified in the #members()
function
#min(n1,n2,...)
#minimum(n1,n2,...)
returns the minimum value among n1, n2, ...
#minute()
returns the current minute (0-59)
#minute(n)
returns the minute portion (0-59)
of the datevalue/timevalue n
#mode(n1,n2,...)
returns the mode value among n1, n2, ...
#month()
returns the current month (1-12)
#month(n)
returns the month (1-12) of the datevalue n
#monthnumber()
returns the current month (1-12)
#monthnumber(a)
returns the month (1-12) for the string a ("January",
"February", ...; or their three-letter abbreviations,
"Jan", "Feb", ...)
#nil()
returns NIL
#not(x)
returns TRUE if x is FALSE (0); else FALSE if x is
TRUE (non-zero)
#now()
returns the current time value (number of seconds since
the Unix Epoch, January 1, 1970)
#odd(n)
returns TRUE, if n is odd; FALSE, if n is even
#or(x1,x2,...)
returns TRUE if any of expressions x1, x2, ... are TRUE
(non-zero); FALSE otherwise (if none of x1, x2, ... are
TRUE or non-zero)
#ord()
[see #code()]
#parse()
[see #split()]
#passwduid()
[see #pwuid()]
#passwdgid()
[see #pwgid()]
#pclose(F)
closes process F; returns TRUE if successful, ERR if
unsuccessful
#pid(a)
#procid(a)
#processid(a)
returns the process id (pid) of process a
#popen(F,a1,a2)
opens process a1 with mode a2 (any of "r", "w", or "a");
returns TRUE if successful, ERR if unsuccessful
#ppid(a)
#pprocid(a)
#pprocessid(a)
returns the parent process id (ppid) of process a
#pwuid(a)
#pwuid(n)
#passwduid(a)
#passwduid(n)
for user name a, or uid n, returns its uid
(as recorded in the system passwd file)
#pwgid(a)
#pwgid(n)
#passwdgid(a)
#passwdgid(n)
for user name a, or uid n, returns its gid
(as recorded in the system passwd file)
#random()
returns a random fraction 0 <= r < 1
#random(n)
returns a random integer from 1 to n
#read(F)
reads in the next line of input from the file handle F
associated with some open file or process; the input
line is assigned to the special variable $rdlin (also
$rdline or $readline); returns the number of chars
read, else ERR on error
#rkeys(a)
#rkeys(a1,a2)
#reversekeys(a)
#reversekeys(a1,a2)
like #keys() (which see), except that the array keys
are sequenced in alphabetically reverse sorted order
#rindex()
[see #rsearch()]
#rfind()
[see #rsearch()]
#round(n)
returns n rounded up or down to the nearest integer
(where #round(2.5) --> 3, #round(-2.5) --> -3)
#rsearch(a1,a2)
#rindex(a1,a2)
#rfind(a1,a2)
return the index position (beginning at 1) of a2
within string a1, with the search starting at the
end of the string, else 0 if not found
#rule()
#rulenumber()
returns 0 when called from within a Pikt script begin
section, 1 when called within the first rule, 2 when
called within the second rule, and so on; if there
are x rules within a Pikt script and #rule() is called
from within an end section, x+1 is returned
#search(a1,a2,n)
#index(a1,a2,n)
#find(a1,a2,n)
returns the index position (beginning at 1) of a2
within string a1, with the search starting at
position n; else 0 if not found
#search(a1,a2)
#index(a1,a2)
#find(a1,a2)
returns the index position (beginning at 1) of a2
within string a1, with the search starting at the
beginning of the string; else 0 if not found
#second()
returns the current second (0-59)
#second(n)
returns the second portion (0-59)
of the datevalue/timevalue n
#skeys(a)
#skeys(a1,a2)
#sortkeys(a)
#sortkeys(a1,a2)
like #keys() (which see), except that the array keys
are sequenced in alphabetically sorted order
#split(a)
#parse(a)
decomposes string a into parts assigned to the variables
$1, $2, ..., where whitespace (space or tab) separates
the constituent parts; returns the number of parts
#split(a1,a2)
#parse(a1,a2)
decomposes string a1 into parts assigned to the variables
$1, $2, ...; if a2 is not a regular expression, any of the
chars in a2 serve as field separators for the decomposition;
else if a2 is a regular expression containing one or more
() pairs, the matching constituent parts are assigned to
$1, $2, ..., and the entire expression match is assigned
to $0; returns the number of parts, or matched parts in
the regular expression case
#split(a1,a2,a3)
#parse(a1,a2,a3)
decomposes string a2 into parts assigned to the variables
a1[1], a1[2], a1[3], ...; a3 is the field separator(s) for
the decomposition; returns the number of parts
#system(a)
executes the command string a, returning its exit value
#timevalue(n1,n2,n3)
returns the time value for the time n1:n2:n3 (hh:mm:ss)
#today()
returns the time value of the current day's beginning
(midnight)
#true()
returns TRUE (1)
#trunc(n)
#truncate(n)
returns n as an integer with its fractional part (if
any) lopped off
#val(a)
#value(a)
returns the numerical value of a; or ERR, if a doesn't
resolve to a number
#weekday()
returns the current weekday (1-7)
#weekday(n)
returns the weekday (1-7) of the datevalue n
#write(F,a)
writes string a to the file handle F associated with
some open file or process; returns the number of
chars written; else ERR on error
#year()
returns the current year (as a four-digit number)
#year(n)
returns the year (as a four-digit number) of the
datevalue n
#yearday()
returns the current yearday (1-366)
#yearday(n)
returns the yearday (1-366) of the datevalue n
#yearweek()
returns the current yearweek (1-53)
#yearweek(n)
returns the yearweek (1-53) of the datevalue n
Next, on to the string functions (functions that return a string value). Where inappropriate in any of the following functions (indeed, in any of the number functions above), a non-integer numerical argument will have the function return the string "<<ERR>>" (and this error will be logged).
$alarm()
$script()
returns the name of the currently executing alarm script
$alert()
returns the current alarm's alert name (e.g., "Urgent")
$ampm()
returns "AM" if the current time falls between
midnight and twelve noon, else "PM" if between noon
and midnight
$ampm(n)
returns "AM" if datevalue n's time falls between
midnight and twelve noon, else "PM" if between noon
and midnight
$basename(a)
for the path a, strips off the directory portion and
returns the terminating filename
$char(n)
returns the char associated with the ASCII value n
$checksum(-3,a)
returns the output of the system cksum command for
file a
$checksum(-2,a)
returns the output of the system sum command for
file a
$checksum(-1,a)
returns the output of the system 'sum -r' command for
file a
$checksum( 0,a)
returns the null checksum (0), also the number of
bytes, for file a
$checksum( 1,a)
returns the BSD-style sum computed internally,
also the number of 512-byte blocks, for file a
$checksum( 2,a)
returns the SysV-style sum computed internally,
also the number of 512-byte blocks, for file a
$checksum( 3,a)
returns the POSIX cksum computed internally,
also the number of bytes, for file a
$checksum( 4,a)
returns the MD4 checksum, also the number of bytes,
for file a
$checksum( 5,a)
returns the MD5 checksum, also the number of bytes,
for file a
note: the checksums assigned to the different levels
might change, and more levels might be added in the
future to compute, for example, these additional
checksums: SNEFRU, HAVAL, SHA
$chop(a,n)
returns string a with n chars chopped off the end
$chop(a)
returns string a minus its last char
$command(a)
executes the command string a, returning its first
line of output only; if more than one output line is
needed, uses #popen() and #read() instead
$dayname()
for the current day, returns the
day name ("Sunday", "Monday", ...)
$dayname(n)
for the given day number (1-7), returns the
day name ("Sunday", "Monday", ...)
$dirname(a)
for the path a, strips off the terminating filename
and returns the directory portion
$dquote()
returns the double quote (") char
$err()
returns "<<ERR>>"
$filemode(a)
for path a, returns its mode in octal format
$fixed(n)
$str(n)
$string(n)
$text(n)
returns #trunc(n) as a string with no decimal places
$fixed(n1,n2)
$str(n1,n2)
$string(n1,n2)
$text(n1,n2)
returns n1 as as string with n2 decimal places
$grname(a)
$grname(n)
$groupname(a)
$groupname(n)
for group name a, or gid n, returns its group name
(as recorded in the system group file)
$grpassword(a)
$grpassword(n)
$grouppassword(a)
$grouppassword(n)
for group name a, or gid n, returns its group password
(as recorded in the system group file)
$grmem(a)
$grmem(n)
$groupmem(a)
$groupmem(n)
$grmembers(a)
$grmembers(n)
$groupmembers(a)
$groupmembers(n)
for group name a, or gid n, returns its group members
(as recorded in the system group file)
$hostname()
returns the system hostname
$if(x,a1,a2)
returns a1, if x is TRUE; else a2, if x is FALSE
$if(x,a)
returns a, if x is TRUE; else the string "<<NIL>>",
if x is false
$inlin()
$inline()
$inputline()
returns the current input line (without the trailing
line-feed; also available as $inlin, $inline, and
$inputline)
$left(a,n)
returns the n left-most chars in a
$level()
returns the current alarm's level (EMERG, ALERT,
CRIT, ERR, WARNING, NOTICE, INFO, DEBUG)
$lower(a)
returns a all in lower-case
$ltrim(a)
returns a stripped of any leading (leftmost) whitespace
$mid()
$middle()
[see $substring()]
$monthname()
for the current month, returns the
month name ("January", "February", ...)
$monthname(n)
for the given month number (1-12), returns the
month name ("January", "February", ...)
$newline()
returns the newline ('\n') char
$nil()
returns "<<NIL>>"
$passwdname()
[see $pwname()]
$passwdpassword()
[see $pwpassword()]
$passwdgecos()
$passwdcomment()
[see $pwgecos()]
$passwddir()
[see $pwdir()]
$passwdshell()
[see $pwshell()]
$proper(a)
returns a with every word capitalized
$pwname(a)
$pwname(n)
$passwdname(a)
$passwdname(n)
for user name a, or uid n, returns its user name
(as recorded in the system passwd file)
$pwpassword(a)
$pwpassword(n)
$passwdpassword(a)
$passwdpassword(n)
for user name a, or uid n, returns its password
(as recorded in the system passwd file)
$pwgecos(a)
$pwgecos(n)
$passwdgecos(a)
$passwdgecos(n)
$pwcomment(a)
$pwcomment(n)
$passwdcomment(a)
$passwdcomment(n)
for user name a, or uid n, returns its gecos/comment
(as recorded in the system passwd file)
$pwdir(a)
$pwdir(n)
$passwddir(a)
$passwddir(n)
for user name a, or uid n, returns its home directory
(as recorded in the system passwd file)
$pwshell(a)
$pwshell(n)
$passwdshell(a)
$passwdshell(n)
for user name a, or uid n, returns its login shell
(as recorded in the system passwd file)
$repeat(a,n)
returns a repeated n times
$replace(a1,a2,n1,n2)
in the string a1, replace the chars from index position
n1 through n2 with the string a2
$reverse(a)
returns string a with its chars reversed
$right(a,n)
returns the n right-most chars in a
$rtrim(a)
returns a stripped of any trailing (rightmost) whitespace
$script()
[see $alarm()]
$space()
returns the space (' ') char
$squote()
returns the single quote (') char
$str()
$string()
[see $fixed()]
$substitute(a1,a2,a3,n)
in the string a1, replace the string a2 with the string
a3, beginning at index position n
$substitute(a1,a2,a3)
in the string a1, replace every instance of the string a2
with the string a3
$substring(a,n1,n2)
$substr(a,n1,n2)
$middle(a,n1,n2)
$mid(a,n1,n2)
for string a, return n2 chars starting at index position n1
$substring(a,n1)
$substr(a,n1)
$middle(a,n1)
$mid(a,n1)
for string a, return all chars to the end of the string
starting at index position n1
$tab()
returns the tab ('\t') char
$task()
returns the current alarm's task description
$text()
[see $fixed()]
$trim(a)
returns a stripped of any leading and/or trailing
whitespace (spaces or tabs)
$upper(a)
returns a all in upper-case
Pikt scripts run from top to bottom, from beginning to end, unless redirected by one of the flow control constructs. Pikt comes with a panoply of flow control structures, most usual, and a few not so usual. ("The usual" below means that the structure follows the usual C-like or Perl-like behavior.)
STRUCTURE DESCRIPTION
if-elif-else-endif the usual (note that you can use elsif and
elseif, too; also, fi instead of endif)
while-endwhile the usual
repeat-until like a do-while loop, except keep looping
until the exit condition is true (no do-
while, because the "do" keyword is reserved
for another use)
for-endfor the usual
for-in-endfor loop through all the keys in the given
associative array
foreach-endforeach loop through all the keys in the given
associative array
break the usual
continue the usual (note that you can use cont, too)
switch-case-default- the usual
breakswitch- (note that you can use breaksw
endswitch and endsw, too)
again repeat the current rule
leave leave the current rule and go on to the next
redo reprocess the current input line
next go on to the next input line
skip n skip ahead n input lines, and resume with
the first rule
last terminate input processing, go to the
end section, if any
pause n pause for n seconds
quit go on to the next alarm script
die "<message>" abort the program (and log the reason)
Statement blocks are indicated by a keyword-keyword combination, for example, if-endif or for-endfor (no { or } here).
Parentheses around conditions are allowed but are unnecessary (and in certain obscure circumstances can even cause syntax errors; it's best to omit them). Below, the left-hand statement has the same effect as the right-hand:
if <cond> if (<cond>)
elif <cond> elif (<cond>)
while <cond> while (<cond>)
until <cond> until (<cond>)
for <init> <cond> <incr> for (<init> <cond> <incr>)
In Pikt scripts (and excepting the script/stanza name, in the leftmost column), every statement begins with a statement keyword. Most you have seen already:
KEYWORD ATTRIBUTE
init
begin
rule
end
status active|inactive
level emergency|alert|critical|error|
warning|notice|info|debug
task "<alarm description>"
input proc|process "<process>"
file "<filename>"
logfile "<filename>"
filter "<process>"
seps|separators "<separators>"
dat|data <data specifier>
keys <var> [<var> ...]
if <conds>
elif|elsif|elseif <conds>
endif|fi
while <conds>
endwhile
repeat
until <conds>
for <set statement> <cond> <set statement>
<string var> in <keys function>
endfor
foreach <keys function>
endforeach
break
continue|cont
switch <string or numerical expression>
case <character or number>
default
breakswitch|breaksw
endswitch|endsw
again
leave
redo
next
skip <numerical expression>
last
pause <numerical expression>
quit
die "<message>"
Here are the rest of the statement keywords:
KEYWORD ATTRIBUTE
do <function call>
set <var> = <expr>
unset|delete <var[]>
exec [wait] "<process>"
output [mail "<message line>"]
[print "<message line>"]
["<logfile>"|piktlog|syslog
"<message line>"]
"do" is akin to a nullop. You use it when when you want to make a function call for its side effects (e.g., when opening a file with #fopen() or splitting a string with #split()) and you don't care about the return value (but maybe you should!).
"set" is for assigning values (either string or numerical expressions) to variables. Performing a set on a dat variable is illegal (input data assigned to dat variables cannot be overridden). Illegal, too, is performing a set on a filehandle (e.g., FOO), preceding variable (e.g., @foo), and previous variable (e.g., %foo); once assigned (in the preceding/previous case by Pikt itself internally), these values may not be changed. An exception is a file handle opened then closed; it may then be reassigned to with a subsequent open function.
A special form of the set action appears in a for statement. For example,
for #i=0 #i<10 #i+=1
The "#i=0" and #i+=1" are implied set actions, but in this context the set keyword is neither necessary nor allowed. To repeat: parentheses, however, are. This is legal:
for (#i=0 #i<10 #i+=1)
Use whatever form you're comfortable with (although using parentheses can, in unusual circumstances, be problematic; it's best not to use them).
The 'unset' keyword (aka 'delete') removes an element from an associative array, for example:
unset $shell["daemon"]
or, equivalently:
delete $shell["daemon"]
"exec" is for executing a command string when you don't care about the return value (otherwise you could use "do #system()" or "do $command()"). "exec wait" temporarily halts the script run until the exec'ed command finishes. You would normally use exec wait, resorting to just plain exec for unusually time-consuming exec'ed commands.
The "output" statement is akin to other language's "print". When used as
output "<message line>"
the text string is sent to the user screen (stdout). Usually, output is sent as e-mail using the statement
output mail "<message line>"
Print output, or output to a logfile, is achieved by inserting the keyword "print", or the appropriate log keyword after the "output". "output piktlog" outputs to the current alert's log file (e.g., Urgent.log), "output syslog" logs to the system log (syslog), and "output log "<logfile>"" logs to the special log file specified
In Pikt, you achieve the functionality of other language's printf() by means of the string formatting functions.
When run non-interactively (i.e., when called without human intervention by piktd), or when run at the command line but with either the +M or +L options, pikt queues mail and print message lines until the end of its program run, only sending out e-mail or dumping to a printer just before pikt terminates. In "log" statements, on the other hand, the message line is logged to the current alert log, to syslog, or the special log file immediately.
There are no practical limits to the number of Pikt script statements, nor to the number of rules. There can be only one init, begin and end section, though.
Pikt regular expressions follow the usual regular expression rules with any necessary clarifications/amplifications to follow.
Here are the regular expression operators:
OPERATOR MEANING
a =~ b string b matches at least one substring within a
a =~~ b like the above, but without case sensitivity
a !~ b string b matches no substring within a
a !~~ b like the above, but without case sensitivity
For example, all of the following are true:
"this is a test" =~ "is"
"this is a test" =~~ "IS"
"this is a test" !~ "THIS"
"this is a test" !~~ "that"
"this is a test" =~ ""
"" !~ "this is a test"
These characters have special meaning within Pikt regular expressions:
CHARACTER(S) MEANING
. matches any single character
* matches zero or more instances of the preceding
character/pattern
? matches zero or one instance(s) of the preceding
character/pattern
+ matches one or more instances of the preceding
character/pattern
{m,n} matches as few as m, or as many as n, instances
of the preceding character/pattern
( ) enclose a subexpression, or set of subexpressions
separated by |
| separates subexpressions (think of "or")
[ ] enclose a set of characters/character ranges
^ as the first character in a [ ] subexpression,
indicates set negation; as the first character
in a regular expression, anchors to the
beginning of the string expression on the
left-hand side of the regexp operator
$ anchors to the end of the string expression
on the left-hand side of the regexp operator
In addition to user-specified character classes, Pikt supports these built-in predefined character classes:
[[:alnum:]] the set of alphanumeric characters
[[:alpha:]] the set of letters
[[:blank:]] tab and space
[[:cntrl:]] the control characters
[[:digit:]] the decimal digits
[[:graph:]] the printable characters except space
[[:lower:]] the lower-case letters
[[:print:]] the printable characters
[[:punct:]] the punctuation characters
[[:space:]] whitespace characters
[[:upper:]] the upper-case letters
Backslash escapes suppress a character's specialness. So, "\\*" is a literal asterisk, and the following are all true:
"fo*bar" !~ "fo*bar" // left side literal string,
// right side regexp
"fo*bar" !~ "fo\*bar"
"fo*bar" =~ "fo\\*bar"
"fo*bar" =~ "\\*"
"*" =~ "\\*"
In any of the above left-hand expressions, you could substitute "fo\*bar", and the statements would all still be true.
Usually, just a single backslash is required for this purpose. In Pikt, however, backslashes are a general escape character. If, for example, you want to output the literal text string "$x" without the $x being interpreted as a variable (which Pikt would attempt to resolve to a value), you would use "\$x". So, if you require a backslash in the final product, you must supply double backslashes going in. Again, see the sample config files for examples of double-backslash usage.
Note that every time a regular expression containing matching parentheses is invoked, for example in any of the following situations
dat "([^:]*):([^:]*)"
if $line =~ "^([^:]*):([^:]*)"
do #split($rdline, "([^:]*):([^:]*)")
you can reference the first parentheses-enclosed matched subexpression with $1, the second with $2, and so on. $0 references the entire matched subexpression.
Note well: The $0, $1, and so on only persist until the next regexp pattern match. The next time you use =~ (or any of the other regexp operators), or the next time you invoke the #split() function (in any of its forms), any previous $0, $1, ... values get supplanted by the values in the latest regexp. You will encounter many strange bugs unless you keep this in mind!
Alternate forms for referencing regexp matches are: $[0], $[1], $[2], and so on. These make it possible to reference the matched expressions within for loops:
set #n = #split($rdlin)
for #i=1 #i<=#n #i+=1
output $[#i]
endfor
Here is a technique for saving $0, $1, ... before a subsequent regexp action:
set #n = #split($rdlin)
for #i=1 #i<=#n #i+=1
set $f[#i] = $[#i]
endfor
...
if $f[3] =~ "cantata|sonata|toccata" // wipes out $3 & $[3] value
output $f[3]
fi
Better still is to use the #split() function (with all three arguments required) this way:
do #split($f, $rdlin, " ")
...
if $f[3] =~ "cantata|sonata|toccata" // wipes out $3 & $[3] value
output $f[3]
fi
If you failed to save the previous regexp values in the $f[] array and simply referenced $3 or $[3], that value would be undefined, since in the =~ test you didn't put ( )'s around any third subexpression, but even if you did (around "toccata") you have lost your previous $3 value.
For further coverage of regular expressions, see the GNU RX info pages.
|