You use the piktc program to preprocess source configuration (.cfg) files on the master machine, and send the post-processing alert (.alt), object (.obj), program, and other files over the network to receiving piktc_svc daemons for installation on the slave systems. Preprocessing entails:
-
stripping out meta-comments (comments of the form // or /* */)
-
#include'ing auxiliary files (e.g., a list of Unix command macros)
-
using #if <os|host|hostgroup> #endif preprocessor directives, filtering through lines pertaining only to the current client (e.g., '#if solaris')
-
using #ifdef <define> #endifdef preprocessor directives, for including/excluding portions of the text (e.g., '#ifdef debug')
-
making macro substitutions (e.g., substituting a Unix command path, with command options, appropriate to the current client)
-
performing an across-the-board syntax check
Note that, in addition to the Pikt script and config files, it is possible to use meta-comments, #include's, #if's, #ifdef's, and macros in managed system configuration files and scripts written in other languages. Note, too, that scripts can rewrite config #include files, raising interesting possibilities for maintaining dynamic system configuration files.
You can customize config files by means of the #if, #elif, #else & #endif preprocessing directives. The format is
#if <machine class>
<lines> [optional]
#elif <machine class> [optional] [or: #elsif, #elseif]
<lines> [optional]
#else [optional]
<lines> [optional]
#endif [or: #fi]
where <machine class> can be a series of host names, host aliases, or host groups, separated by either the |, &, or ! set operators. | indicates set union, & set conjunction, and ! set complement. You can also use parentheses, ( and ), in the class specifications.
#elsif and #elseif are alternate forms of #elif, and #fi is interchangeable with #endif.
Rather than #if, #elif, #else, and #endif, you may instead use their synonyms #ifsys, #elifsys, #elsesys, and #endifsys if you prefer. You may freely intermix these alternate forms, but for clarity's sake, it's probably best not to (for example, don't use #if-#endifsys, or #ifsys-#else-#endif).
You can nest #if-#endif's up to sixty four levels deep.
Akin to #if, a second class of preprocessor directives consists of: #ifdef, #ifndef, #elifdef, #elifndef, #elsedef, #endifdef, #define, #undefine, and #setdef. The format is
#ifdef [!] <define> [also: #ifndef]
<lines> [optional]
#elifdef <define> [optional] [or: #elsifdef, #elseifdef]
[also: #elifndef, #elsifndef,
#elseifndef]
<lines> [optional]
#elsedef [optional]
<lines> [optional]
#endifdef [or: #fidef]
#define <define> [or: #def]
#undefine <define> [or: #undef]
#setdef <define> = TRUE|true|YES|yes|ON|on|1|
FALSE|false|NO|no|OFF|off|0|
[!] <define>|
[!] [<proc>]
where <define> is an identifier representing a form of logical switch that is either defined (true) or undefined (false).
#ifdef means "if <define> is true". #ifndef means "#ifdef [not] <define>", or in other words, "if <define> is not true".
#ifdef ! <define> is equivalent to #ifndef <define>. Similarly, #ifndef ! <define> is equivalent to #ifdef <define>.
#setdef <define> = TRUE is equivalent to #define <define>. Similarly, #setdef <define> = FALSE is equivalent to #undefine <define>.
#setdef <define> = ! TRUE is equivalent to #setdef <define> = FALSE. Similarly, #setdef <define> = ! FALSE is equivalent to #setdef <define> = TRUE.
You may #setdef a <define> equal to the exit status (TRUE or FALSE) of any process, for example
#setdef paranoid = [test `hostname` = "kerberos"]
Logical defines are set (to TRUE) or unset (to FALSE) in any of three ways: (a) in the file defines.cfg; (b) in any config file, except systems.cfg or defines.cfg, by means of the #define, #undefine, or #setdef directives; or (c) at the command line, by means of either the +D (sets to TRUE) or -D (unsets to FALSE) switches. (See defines.cfg and piktc for further explanation.)
#def(ine), #undef(ine), and #setdef statements in the config files have the highest precedence, and command-line defines/undefines override any settings in the defines.cfg file.
If you #def(ine), #undef(ine), or #setdef an identifier in a config file, else use -/+D <identifier> at the command line, without first declaring that identifier in defines.cfg, that will generate an error.
You can set and unset a define throughout the config files by a succession #def(ine), #undef(ine), and #setdef preprocessor statements. #def(ine), #undef(ine), and #setdef statements have global effect. That is, if you set (or unset) a define in one file, it remains set (or unset) through the remainder of that file and on into the next (unless or until you change its setting by a new #def(ine), #undef(ine), or #setdef directive).
If you want to turn on debugging temporarily, then switch it back to its former state, you could do this
#ifdef debug
# define deftmp // deftmp must be registered in defines.cfg
#elsedef
# undefine deftmp
#endifdef
#define debug
...
#ifdef deftmp
# define debug
#elsedef
# undefine debug
#endifdef
Or, you can achieve the same effect in this shorthand form:
#setdef deftmp = debug
#define debug [or: #setdef debug = TRUE]
...
#setdef debug = deftmp
In other words, you can assign one switch's logical value to another's by using #setdef <define1> = <define2>. (In the above example, it is irrelevant what you set deftmp to in defines.cfg. If you fail to register deftmp--or any other define--in defines.cfg, and simply reference it later on in your config files, the preprocessor will complain about an "unknown define".)
#elsifdef and #elseifdef are alternate forms of #elifdef, #elsifndef and #elseifndef are alternate forms of #elifndef, #define is synonymous with #def, #undefine is synonymous with #undef, and #fidef is interchangeable with #endifdef.
You can nest #ifdef-#endifdef's up to sixty four levels deep.
You can nest #ifdef's within #if's, and vice-versa, throughout the config files. Per-machine #if directives take precedence over logical #ifdef directives. So, for example,
#if linux
[ aaa ... ]
#ifdef paranoid
[ bbb ... ]
#endifdef
[ ccc ... ]
#endif
would have the aaa, bbb & ccc content appear only on linux machines, and the bbb content only if paranoid is set (defined). On the other hand, with
#ifdef paranoid
[ aaa ... ]
#if linux
[ bbb ... ]
#endif
[ ccc ... ]
#endifdef
the aaa and ccc content would appear on all machines, if paranoid is set, while the bbb content would appear only on linux machines with paranoid defined.
This, however, would lead to error on non-linux systems:
#ifdef paranoid
[ aaa ... ]
#if linux
[ bbb ... ]
#endifdef
[ ccc ... ]
#endif
because there would be no concluding #endifdef on those machines. In other words, be careful about intertwining #if-#endif's with #ifdef-#endifdef's.
Observe that you can set/unset defines on a per-machine basis in the defines.cfg file, for example
#if dbserver
paranoid TRUE
#else
paranoid FALSE
#endif
Or, alternatively and equivalently,
paranoid
#if dbserver
TRUE
#else
FALSE
#endif
Enclosing portions of a config file within #fix-#unfix (alternate forms: #freeze-#unfreeze) preprocessor directives has the effect of fixing (freezing) those sections of the file, thereby preventing comment elimination and macro expansion. You can fix individual lines, stanzas, or even entire files in this way.
A config file can incorporate one or more other files by means of the #include directive. Included files may themselves include other files, but only of the same basic configuration type (macro files include macro files, for example). (If you nest #include's too deeply, you will bump up against the system's limit on the number of simultaneously open files.)
Although #if-#endif's, #ifdef-#endifdef's, and #fix-#unfix's can span across included files (e.g., you could begin an #if in one file and close it with an #endif in another), it is not good practice to do so. In any case, an #if (or #ifdef) in one config file may not be #endif (or #endifdef) terminated in a subsequent config file of a different type.
The #include directive takes three forms, for example,
#include "/pikt/contrib/lib/configs/security_alarms.cfg"
and
#include <security_alarms.cfg>
and
#include <alarms/security_alarms.cfg>
where the file in the second example is found in the default config file directory =piktdir/lib/configs, and the third is located in the =piktdir/lib/configs/alarms directory.
You may name included config files anything you want--i.e., they don't have to end with the .cfg file extension--but the eight main config files must remain systems.cfg, defines.cfg, macros.cfg, alarms.cfg, alerts.cfg, programs.cfg, files.cfg, and objects.cfg, and these eight config files must reside in the =piktdir/lib/configs directory.
Macro substitutions do not apply to preprocessor directives, so the following (unfortunately) doesn't work:
#include "=contribdir/security_alarms.cfg"
An active #fix directive does not apply to any include files. If you want to turn off comment stripping and macro expansion within an include file, you must employ #fix-#unfix within that include file.
Includes are especially useful for compartmentalizing across different systems administrators (where each has his/her own sub-config file), and across functions (e.g., security alarms in one file, network alarms in another), and for including files contributed by outsiders. Includes are also good for quarantining information about different operating systems (e.g., you could isolate Unix command macros for different operating systems in their own, separate include files). You must take care, however, not to multiply your config files to the point where they become hard to manage.
One perhaps troublesome requirement for #include'ing program and other files is that you must indent them. You may use the #indent ... #unindent preprocessor directives to fake indentation. More precisely, enclosing entire files, or portions of files, with #indent-#unindent inserts an automatic single-tab indent at the beginning of every enclosed line.
For example, piktc interprets this
#indent
#!/usr/local/bin/expect
set timeout [lindex $argv 0]
eval spawn -noecho [lrange $argv 1 end]
expect
#unindent
in the same way that it sees this
#!/usr/local/bin/expect
set timeout [lindex $argv 0]
eval spawn -noecho [lrange $argv 1 end]
expect
You can do this
maxtime.exp
#indent
#include <maxtime.exp_programs.cfg>
#unindent
to achieve indentation without actually having to indent the maxtime.exp program file.
#indent-#unindent is also very useful when applied to any of the .obj files. For example
passwd
#indent
#include "/etc/passwd"
#unindent
would allow you to use the system passwd file as a PIKT object set without your having to worry about actual indentation.
For greater clarity, you may indent your preprocessor lines--that is, put spaces (or tabs) between the '#' and the preprocessor keyword. Note that this has nothing to do with the indenting just discussed.
For example, you could render complex preprocessor code like
#ifdef paranoid
#if solaris
#ifdef doexec
[code...]
#elsedef
[code...]
#endifdef
#else
[code...]
#endif
#elsedef
[code...]
#endifdef
instead as
#ifdef paranoid
# if solaris
# ifdef doexec
[code...]
# elsedef
[code...]
# endifdef
# else
[code...]
# endif
#elsedef
[code...]
#endifdef
Recall that #fix directives do not extend into include files. So one problem with the passwd example just given is: What about apparent macros and comment sequences (//, /*, */) within the #include file? Also, what about lines that appear to be PIKT preprocessor directives (e.g., '#if vienna') but in fact are not?
The solution for these sorts of problems is the #verbatim directive. #verbatim is like #include but with auto-indentation, no comment stripping or macro expansion, and suppression of other preprocessor directives. In other words, the line
#verbatim "/etc/passwd"
is like
#include "/etc/passwd"
where /etc/passwd is interpreted to be
[#fix] [implied]
[#indent] [implied]
root:0:... [actual passwd file "as is"; PIKT-like
... preprocessor directives and macros within
are ignored]
[#unindent] [implied]
[#unfix] [implied]
So, this is actually the safest and preferred way to include a file like /etc/passwd "as is":
passwd
#verbatim "/etc/passwd"
You may #include (or #verbatim) process output within PIKT .cfg files, not just file content. This variant was added to address the difficult question: In a constantly changing systems environment, how do you manage, automatically if possible, to keep your PIKT configurations accurate and up-to-date?
The formats are
#include [<proc>] [or: #verbatim]
#include "<file>" [<proc>] [or: #verbatim]
#include <<file>> [<proc>] [or: #verbatim]
As an example of the first format, you could do something like the following (in defines.cfg):
dst // TRUE if Daylight Savings Time now applies,
// FALSE otherwise
// at our site, `date +%Z` returns "CDT" if DST is in
// effect, "CST" otherwise; substitute your own time
// zone string as needed
#verbatim [if [ `date +%Z` = "CDT" ]; then echo TRUE; else echo FALSE; fi]
The process (within enclosing [] brackets)
if [ `date +%Z` = "CDT" ]; then echo TRUE; else echo FALSE; fi
will output either TRUE or FALSE depending on whether Daylight Savings Time applies. So, sometimes the preprocessor will see, in effect,
dst TRUE
and at other times
dst FALSE
In this example, you don't have to worry about keeping the dst define up-to-date. The #include [<proc>] feature will do it automatically for you.
Another thing hard to keep up-to-date is your down systems list. Suppose you have a program, called downsys.pl, to determine your down systems. When invoked as 'downsys.pl 10' (where 10 is a timeout factor in seconds), typical downsys.pl output might be
geneva
cracow
padua
You could then define downsys in your systems.cfg this way:
downsys // set '-H downsys' for these
members
#indent
#include [/pikt/lib/programs/downsys.pl 10]
#unindent
Note that it is necessary to indent your [<proc>] output, whether implicitly with the #verbatim directive or explicitly by wrapping '#indent ... #unindent' around the #include directive (or perhaps by programming indentation into the process output itself), else the lack of indentation will cause a parser error.
downsys.pl might come up empty-handed, because it aborts due to a syntax error, or a timeout, or perhaps because there are no current down systems. In most cases--a 'members' stanza in systems.cfg being one exception--this will cause a parse error. To protect against this, it is entirely possible to intersperse #include [<proc>] directives with hard-coded components and other preprocessor directives:
downsys // set '-H downsys' for these
members
london
rotterdam
calais
#verbatim [/pikt/lib/programs/downsys.pl 10]
#include <systems/retiredsys_systems.cfg>
Suppose the retiredsys_systems.cfg file content were
rheims
orleans2
(Note that these systems are indented within the file, hence it is unnecessary to wrap '#indent ... #unindent' around the #include directive, nor is it necessary to use #verbatim.)
After all file and process inclusions, the preprocessor would see, in effect,
downsys
members
london
rotterdam
calais
geneva
cracow
padua
rheims
orleans2
In the example just given, downsys.pl would be run every time the preprocessor sees that line in the configuration (in systems.cfg). If you have many systems to poll, that would of course slow things down considerably. You have the option to reference both an on-disk local file, as well as a process, with
#include "<file>" [<proc>]
#include <<file>> [<proc>]
Either of these will have the preprocessor reference the on-disk <file> unless you specify the '-I' option in the piktc (preprocessor) command, in which case the [<proc>] output will be referenced, and that output will update the current <file> contents.
You might think of the on-disk <file> as a sort of data cache for [<proc>].
In other words, suppose the directive were written as
#verbatim <systems/downsys_systems.cfg> [/pikt/lib/programs/downsys.pl 10]
If you specify the '-I' (for "update include files") piktc option, as in
# piktc -cI +H piktmaster
this will read from [/pikt/lib/programs/downsys.pl 10] and write that process output to the file <systems/downsys_systems.cfg>. If you omit the '-I' option, piktc will just reference the <systems/downsys_systems.cfg> file.
Although you could use the '-I' routinely in all of your piktc commands, of course you shouldn't, for that would slow your piktc operations down dramatically. Better to do this just occasionally, say once a day (overnight) only.
To auto-update your configuration and refresh your scripts and data sets with the very latest and most accurate information on all systems, you could regularly schedule (e.g., nightly) the sequence
piktc -cI +H piktmaster
piktc -iv ALL
either by cron'ing it or by having PIKT itself run it on the piktmaster machine.
When using '#include [<proc>]', you must be careful to use a <proc> that will not hang the whole 'piktc -I' process (because, for example, you are ssh'ing to a remote host and that host is down). To force a timeout, you might use this Expect script (as it would appear in programs.cfg):
maxtime.exp // put a time limit on an invoked command
#!=expect
set timeout [lindex $argv 0]
eval spawn -noecho [lrange $argv 1 end]
expect
You might use this Expect script in downsys.pl written as:
#if piktmaster
downsys.pl // list all down systems
#!=perl
# downsys.pl -- list all down systems
die "Usage: downsys.pl <secs timeout>\n" if ($#ARGV != 0) ;
foreach $sys (`=piktc -L +H all`) {
chomp $sys ;
next if (`=maxtime $ARGV[0] =ping $sys` =~ /is alive/) ;
print "$sys\n" ;
}
#endif
If you are not careful, it's very easy to get the <proc> wrong in various ways and screw up your #include or #verbatim files, therefore parts of your overall PIKT configuration. It's worth taking the trouble, however, because auto-updating #include and #verbatim file directives is an elegant solution to the problem of keeping up-to-date various dynamic PIKT configuration components.
Refer to the
Introduction
pages, the Samples pages, or the lib/configs_samples files in the latest official software release, pikt-current.tar.gz (but view the cautionary README file in the configs_samples directory)
for many more examples of this technique.
What if you want to run a process but don't really want to #include anything at that point? You can use this
#include "/dev/null" [<proc>]
Or, you can use its effective equivalent
#exec "/dev/null" [<proc>]
Because you have specified a file (/dev/null), [<proc>] will only be run if you are doing a 'piktc -I' (update #include/#verbatim files) operation.
If you want to run [<proc>] every time (whether or not you are using the piktc -I option), you would use
#exec [<proc>]
One possible use of #exec would be to run some external process that updates a series of #include files referenced later on in the PIKT configuration.
#piktexec (and its equivalent, #pexec) is similar to #exec except that for the [<proc>], you specify any legitimate Pikt statement sequence. For example, you might do this (in systems.cfg):
sudo
members
#verbatim <systems/sudo_systems.cfg> [/pikt/bin/piktc -xI +C
"=ls /etc/sudoers 2>/dev/null | =grep sudoers >/dev/null &&
=hostname" -H downsys 2>/dev/null]
#piktexec "/dev/null" [if =wednesday =mailmsg1(review the sudo group,
REMINDER, =piktadmin) fi]
The above says to populate the sudo host group with machines identified as sudo machines by the presence of an /etc/sudoers file. Then, in the #piktexec directive, if today is Wednesday, e-mail a short reminder message, "review the sudo group," to the PIKT sysadmin. Because of the "/dev/null", this reminder is only sent (on Wednesdays) during any 'piktc -I' program run.
Note that you may use PIKT macros (for example, =mailmsg1()) in your #piktexec [<proc>].
Another example:
#pexec "/dev/null" [=remind(2001, 6, 30, reactivate the Foo alarm,
REMINDER, =piktadmin)]
If you left out the "/dev/null", and if this were June 30, 2001, the reminder message would be sent and resent with every piktc run that day, which is probably not what you want.
#exec and #piktexec were inspired by the felt need to deal with the general reminder problem. You have this capability, in alarms.cfg, by means of something like the =remind() macro. #piktexec extends this sort of capability to all the other PIKT .cfg files.
It will be interesting to discover other uses for #exec, #piktexec, and the whole idea of running processes from within the PIKT configuration.
Using #exec and #piktexec correctly requires subtlety and care on your part, especially when it comes to proper quoting. A good rule of thumb: Avoid using single quotes (') anywhere within a #piktexec directive.
With #set, and its variant #setenv, you can set environment variables. You can then reference these environment variables in subsequent preprocessor directives, such as #exec, #include, #verbatim, and #echo. (Note that legal environment variable names begin with a letter, followed by any sequence of letters, digits, and/or _ (underscore).)
So, for example, if you use
#set foo = "bar"
or the variant
#setenv foo "bar"
you could access the value of the $foo environment variable in a subsequent preprocessor directive.
If you don't specify a value, as in
#set foo
the environment variable is implicitly set to 1. So the previous #set directive is equivalent to
#set foo = "1"
and
#setenv foo "1"
#unset and #unsetenv undo the effects of the #set and #setenv directives. So
#unset foo
or
#unsetenv foo
leaves the environment variable $foo undefined.
You may set (or unset) environment variables in this way to #include (or not) files (i.e., processes cat'ing the file(s)), or to access environment variable values using the #echo command, to pass parameters to processes embedded within preprocessor directives (e.g., #include [<proc>], or #exec [<proc], or to do some other clever thing.
For example, if you do this in one of your config files
#set foo
#include <files/this_files.cfg>
#unset foo
you could have in this_files.cfg
#include [test $foo && /bin/cat /pikt/lib/configs/files/that_files.cfg]
or perhaps #include the output of some process
#include [/usr/local/bin/bar.pl]
where bar.pl references the $foo environment variable, for example with
if ($ENV{foo}) {
...
}
Together with the piktc -|+ E (un)set environment variable option, #set, #setenv, #unset, #unsetenv and the other preprocessor directives make a very powerful combination. For some examples, refer to
Introduction: Formatting a Document,
and Samples, also Developer's Notes 1.17.0 and Developer's Notes 1.17.0 at the Changes page.
You may reference an environment variable specified in an earlier #set or #setenv directive in this way:
#include [/bin/echo "$foo"]
which would have the same effect as the line
bar
assuming you had set foo equal to "bar" in an earlier #set or #setenv directive.
Alternatively, you could use the #echo directive:
#echo "$foo"
This would be useful to achieve a form of crude parameter passing to #include files.
For example, suppose you manage a Web site, and you have a common header #include file that is mainly identical across all of your pages except for a few details, such as the page title. If you set the environment variable $title with the directive
#set title = "ACME Widgets: Product Information"
you may subsequently reference $title in the header #include file with the directive
#echo "<title>$title</title>"
After all preprocessing, the line would effectively be
<title>ACME Widgets: Product Information</title>
See the pikt.org website for demonstrations of this advanced technique.
piktc is selective about which parts of the PIKT configuration it preprocesses. piktc always looks at systems.cfg, and usually (but not always) at defines.cfg and macros.cfg. As for the other config files, it depends on the operation. For instance, for a 'piktc -i +A ... +H ...' operation, only alerts.cfg and alarms.cfg are additionally preprocessed. For a 'piktc -i +O ... +H ...' operation, only objects.cfg and its #include files are preprocessed; alerts.cfg, alarms.cfg, programs.cfg, and files.cfg are all bypassed.
So when doing piktc file operations, for example 'piktc -iv +F ...', much time is saved by ignoring the irrelevant .cfg files (alerts.cfg, alarms.cfg, etc.). But what if you only need to install (or diff) a single file among hundreds of files? (Suppose, for instance, that you use PIKT to manage a large document collection, such as a Web site containing hundreds and hundreds of HTML pages.) It can still take piktc a very long time to churn through files.cfg and its hundreds of #include files. What to do?
It just so happens that there is no effective difference between doing this
#include <files/some_files.cfg>
and instead doing this
#include [/bin/cat /pikt/lib/configs/files/some_files.cfg]
Whether you directly #include a file or #include a process cat'ing the file makes no difference to piktc. Using this fact, you can trick piktc into conditionally including files, looking at only certain of the files.cfg #include files and ignoring all the rest.
In your top-level files.cfg, you might include Web pages by means of
...
#include <files/www/pages_files.cfg>
...
Suppose you do your pages_files.cfg this way:
#include [test "$pages" = "index" &&
/bin/cat /pikt/lib/configs/files/www/pages_index_files.cfg]
...
#include [test "$pages" = "products" &&
/bin/cat /pikt/lib/configs/files/www/pages_products_files.cfg]
...
#include [test "$pages" = "contact" &&
/bin/cat /pikt/lib/configs/files/www/pages_contact_files.cfg]
If the environment variable $pages is set to "index", you #include the index.html #include file, pages_index_files.cfg. Otherwise, if $pages is set to something else or is not set at all, the test fails, and the command following the '&&' is not run--so you effectively #include nothing.
Now, what if you do this?
piktc -iv +E pages=index +F index.html +H webserver
Because $pages is set to "index", this line includes content
#include [test "$pages" = "index" &&
/bin/cat /pikt/lib/configs/files/www/pages_index_files.cfg]
while all the rest do not. By this trick, you have instructed piktc to look only at the pages_index_files.cfg file and ignore the #include files for all other pages.
In fact, you can pursue this further, for example doing this in your files.cfg:
...
#include [test $adm && /bin/cat /pikt/lib/configs/files/adm/admin_files.cfg]
#include [test $man && /bin/cat /pikt/lib/configs/files/man/manuals_files.cfg]
#include [test $www && /bin/cat /pikt/lib/configs/files/www/pages_files.cfg]
...
And, for example, doing this in your pages_files.cfg:
#include [(test "$pages" = "index" || test "$pages" = "all") &&
/bin/cat /pikt/lib/configs/files/www/pages_index_files.cfg]
...
#include [(test "$pages" = "products" || test "$pages" = "all") &&
/bin/cat /pikt/lib/configs/files/www/pages_products_files.cfg]
...
#include [(test "$pages" = "contact" || test "$pages" = "all") &&
/bin/cat /pikt/lib/configs/files/www/pages_contact_files.cfg]
So, if you only want to install your Web site's home page, you would issue the command
piktc -iv +E www pages=index +F index.html +H webserver
Because the $www environment variable is set (by default to "1", when you don't specify a value), you effectively #include pages_files.cfg (but not admin_files.cfg, manuals_files.cfg, ...). And because $pages is set to "index", you effectively #include pages_index_files.cfg (and piktc ignores all the other pages_*_files.cfg).
If you have PIKT manage a large collection of files, it could take piktc many minutes to churn through hundreds of #include files (some of which themselves might #include files or process output, etc.). Now, by means of the techniques described above, you can preprocess and install a single file (for example, index.html) or subset of files in a matter of seconds.
Here is a command to install all Web pages:
piktc -iv +E www pages=all +F all +H webserver
Here is a command to diff just the home page:
piktc -fv +E www pages=index +F index.html +H webserver
Here is a command to MD5 checksum all Postfix-related admin files (e.g., main.cf, access, master.cf, etc.):
piktc -m5v +E adm files=postfix +F =adm_postfix +H mailserver
where you '[test "$files" = "postfix" ...]' within an #include directive in admin_files.cfg, and where =adm_postfix is a list of Postfix files that you have designated in macros.cfg.
These are advanced PIKT techniques. If your PIKT configuration is small and simple, you would probably have little use for this +/-E, testing environment variables, and cat'ing #include files complexity. But if your PIKT configuration is massive and complicated, these techniques solve a significant scaling problem and potentially save you much time.
All the piktc options (E, D, and so on), the many different preprocessor directives (#if, #ifdef, #include, #set, #echo, and so on), the standard Unix/Linux tools (shell commands, scripts, and so on)--when joined together make for an extremely powerful combination. Use them or not--the choice, and the ingenuity, is all yours.
Refer to the
Introduction
pages or the Samples pages for more examples of these advanced techniques.
Preprocessor directives may appear in any config file except systems.cfg.
#include and #verbatim directives may appear in systems.cfg, but all other preprocessor directives are forbidden. Macros, too, are outlawed in systems.cfg. Comments, however, can appear in systems.cfg and in any other .cfg file.
You may use #ifdef, #ifndef, and #setdef in defines.cfg just so long as the referenced define is specified earlier in defines.cfg.
Referencing an unregistered #define (for example, '#ifdef foobar' where 'foobar' has not been #define'd in defines.cfg) will produce an error and halt all further piktc preprocessing. You may reference unregistered operating systems in #if statements (for example, '#elif aix | irix', where 'aix' and 'irix' are not mentioned in systems.cfg). (This is to make it easier to distribute "standard library" config files written for all operating systems.) If you set 'verbose_log' to 'YES' in PIKT.conf, referencing an unregistered operating system (or hostname or host alias) in an #if statement will write a WARNING message to piktc.log but not otherwise interfere with piktc preprocessing.
When attempting to debug parse errors, keep in mind the following:
-
"parse error (in defparse())" suggests that you have a mistaken #ifdef-#endifdef construction somewhere (e.g., you omitted an ending #endifdef).
-
"parse error (in tagparse())" suggests that you have a mistaken #if-#endif construction somewhere (e.g., you used #if where you should have used #elif).
-
"parse error (in cfgparse())" suggests that you have a garbled your Pikt script syntax (e.g., you have misspelled a keyword, or forgotten to close a quoted string, or ...).
-
If none of the above applies, perhaps you have, for a machine or OS, omitted a stanza identifier or stanza contents; or failed to close a #fix-#unfix; or used '#if sys1 || sys2' instead of '#if sys1 | sys2'; or ... Really, there are many ways you could go wrong, but if a parse error is reported, 99.99% of the time it's for real.
-
Sometimes parse errors reported in one file actually stem from errors made at or near the end of the previous file. The piktc preprocessor processes files in the following order: systems.cfg, defines.cfg, macros.cfg, alarms.cfg, alerts.cfg, programs.cfg, files.cfg, objects.cfg. So a parse error reported at the beginning of objects.cfg might actually be caused by a goof at the end of files.cfg. Or an error reported at the beginning of an #include file might actually be just before the #include directive in the calling parent file.
-
Make sure that the last line of every config file ends with a line feed!
-
Remember that when debugging parse errors, comments are your friend. Try using /* */ comments to nullify entire sections of the configuration. If your problem goes away, the fault is in the commented-out section. Be sure not to nest /* */ comments, of course!
-
Be on friendly terms, too, with backslashes ('\'). Sometimes they will get you out of a parsing jam. For example, '#if' at the beginning of a line in an #indent'ed and #include'd Perl or shell script will cause a parser error. Changing to '\#if' or '#\if' will fix the problem.
-
When a parse error involves @foo or %foo references, try coercing the data type with, e.g., #val(@foo) or $text(%foo).
-
When all else fails and you are stuck trying to debug a convoluted configuration, throw all or much of it away and start again from scratch. Issue a 'piktc -cv +H ...' every step along the way to validate your most recent changes.
|