4.12. Using Command Line and Printcap Options In Filters

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.



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.