One of the problems commonly encountered problem in writing a filter is getting the command line values. The UNIX POSIX Standard provides a C Language getopt function that can be used for command line options, and some, but not all shell implementations have a corresponding shell getopt function. Also, many times it would be useful to get the values of the printcap options. These could be used to specify options or operations that are not easily done by passing command lines.
Observe that all the command line options are single letters. If we set the shell variables to the corresponding option value, then we could access them by using $x, where x is the option letter. There is an exception to this rule, which is the -c command line literal, which for various historical and compatibility reasons does not take a value. But if it is present, we might as well assign it the value 1.
Observe that by convention all printcap options have lowercase names of two or more letters, and that all environment variables have all upper case letters. If we set shell variables with the corresponding printcap entry values, then we can access them using $literal. If we need to create a local shell variable for use, we can use mIxEd case and not have a conflict.
The decode_args_with_sh script which is in the UTILS directory of the LPRng distribution follows these conventions and sets the appropriate shell variables. We have also include a bit of code that will extract the control file control line values and put them into variables as well.
Save the current /tmp/testf filter file in /tmp/testf.old and replace it with the following:
#!/bin/sh # this is an example of how to use /bin/sh and LPRng # to get the command line and printcap option values # and set shell variables from them # Note that we use a couple of variables #PATH=/bin:/usr/bin Args="" vAr="" vAlue="" vAls="" iI="" Tf="" Debug=1 if -n $Debug ; then set >/tmp/before fi Args="$@" if -n $Debug ; then echo "$@" >>/tmp/before fi while expr "$1" : '-.*' >/dev/null ; do vAr=`expr "$1" : '-\(.\).*'`; vAlue=`expr "$1" : '-.\(.*\)`; case "$vAr" in - ) break;; c ) c=1;; [a-zA-Z] ) if test "X$vAlue" = "X" ; then shift; vAlue=$1; fi; eval $vAr='$vAlue'; #setvar $vAr "$vAlue" ;; esac; shift; done # set shell variables to the printcap options # flag -> flag=1 # flag@ -> flag=0 # option=value -> option='value' # setpcvals () { while test "$#" -gt 0 ; do iI=$1 if expr "$iI" : " *\:" >/dev/null ; then vAr=`expr "$iI" : " *\:\([^=][^=]*\)=.*"`; vAlue=`expr "$iI" : " *\:[^=][^=]*=\(.*\)"`; if test "X$vAr" = "X" ; then vAr=`expr "$iI" : " *:\(.*\)@"`; vAlue=0; fi if test "X$vAr" = "X" ; then vAr=`expr "$iI" : " *:\(.*\)"`; vAlue=1; fi if test "X$vAr" != "X" ; then eval $vAr='$vAlue'; #setvar $vAr "$vAlue" fi else vAr=`expr "$iI" : " *\([^|][^|]*\).*"`; if test "X$vAr" != "X" ; then eval Printer="$vAr" fi fi; shift done } # set shell variables to the printcap options # flag -> flag=1 # flag@ -> flag=0 # option=value -> option='value' # setcontrolvals () { while test "$#" -gt 0 ; do iI=$1 vAr=`expr "$iI" : " *\([A-Z]\).*"`; vAlue=`expr "$iI" : " *[A-Z]\(.*\)"`; if test "X$vAr" != "X" ; then eval $vAr='$vAlue'; #setvar $vAr "$vAlue"; fi; shift done } Tf=$IFS IFS=" " setpcvals $PRINTCAP_ENTRY setcontrolvals $CONTROL IFS=$Tf # # restore argument list set -- $Args Args="" vAr="" vAlue="" vAls="" iI="" Tf="" if test -n "$Debug" ; then set >/tmp/after echo "$@" >>/tmp/after diff /tmp/before /tmp/after fi /bin/cat exit 0
Lets print our /tmp/hi test file and then look at the results in /tmp/lp:
h4: {181} % cp /dev/null /tmp/lp h4: {182} % lpr /tmp/hi h4: {183} % more /tmp/lp 0a1 > e=dfA021771h4.private 2a4,6 > l=66 > s=status > L=papowell 10a15,17 > j=021771 > C=A > J=/tmp/hi 12a20 > a=acct ... 33a58 > Printer=lp ... hi
As we see from the output, shell variables have the values of our command line and printcap options. It is left as an exercise for the reader to add the necessary export statements to cause these values to be exported to subshells. It is not recommended that a wholesale export of the shell variables be done, but only selected ones.
The paranoid and security minded reader will see some possible security problem with this script. The eval $vAr='$vAlue' command sets the value of the shell variable $vAr to the value $vAlue. The $vAr variable is always taken from either a single letter or is the name of an option in the printcap file. Clearly the printcap file must not be modifiable by users, and should have the same security considerations as any other system configuration file. The values of the $vAlue are taken directly from the control file, whose contents are under the control of the originator of the print job request.
For this reason LPRng takes the rather brutal step of sanitizing the control file. Only alphanumerics or a character in the list @/:()=,+-%_ are used in the control file; all others replaced by the underscore (_) character. In addition, all filters are run as the lpd user specified in the lpd.conf configuration file.
The following is an example of how to extract the same information in Perl:
#!/usr/bin/perl eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if $running_under_some_shell; # this emulates #! processing on NIH machines. # (remove #! line above if indigestible) use Getopt::Std; my(%args,%options); # get the arguments getopt( "a:b:cd:e:f:g:h:i:j:l:m:n:o:p:q:r:s:t:u:v:w:x:y:z:" . "A:B:C:D:E:F:G:H:I:J:L:M:N:O:P:Q:R:S:T:U:V:W:X:Y:Z:", \%args ); # set :key=value -> $option{$key}=$value # set :key@ -> $option{$key}="0" # set :key -> $option{$key}="1" map { if( m/^\s*:([^=]+)=(.*)/ ){ $options{$1}=$2; } elsif( m/^\s*:([^=]+)\@$/ ){ $options{$1}="0"; } elsif( m/^\s*:([^=]+)/ ){ $options{$1}="1"; } elsif( m/^\s*([^|]+)/ ){ $options{"Printer"}=$1; } } split( "\n", $ENV{'PRINTCAP_ENTRY'}); # get the control file entries map { if( m/^\s*([A-Z])(.*)/ ){ $options{$1}=$2; } elsif( m/^\s*([a-z])/ ){ $options{'Format'}=$1; } } split( "\n", $ENV{'CONTROL'});
The Perl Getopt::Std routine parses the command line options and puts their values in the %args hash variable where they can be accessed using $args{'x'}. Similarly, the map and split functions process the PRINTCAP_ENTRY and CONTROL environment variable and set %options with the printcap entry options and the values from the control file. The map function could be replaced by a foreach loop, but this is Perl: There is more than one way to do it and no tutorial would be complete without at least one mind stretching example that has the reader reaching for the reference manual.