InhaltWeiter

9. Gemeinsame Nutzung eines Windows-Druckers

Um einen an einem Windows-Rechner angeschlossenen Drucker auch von Linux aus nutzen zu können, muß folgendermaßen vorgegangen werden:

  1. In der Datei /etc/printcap müssen die passenden Einträge vorhanden sein. Diese müssen zur lokalen Verzeichnisstruktur, z.B. zum Spool-Verzeichnis, passen.
  2. Das Skript /usr/bin/smbprint muß vorhanden sein. Es ist Bestandteil des Quellcodes von Samba. Dieses Skript liegt aber nicht allen binären Distributionen bei. Deshalb folgt unten die Vorstellung einer leicht geänderten Kopie des Skripts.
  3. Sollen ASCII-Dateien nach Postscript konvertiert werden, dann muß nenscript oder ein vergleichbaren Produkt vorhanden sein. Bei nenscript handelt es sich um ein Postscript-Konvertierprogramm, das im allgemeinen im Verzeichnis /usr/bin installiert ist.

Der folgende /etc/printcap Eintrag ist für einen HP 5MP Drucker gedacht, der an einen WindowsNT-Rechner angeschlossen ist. Doch zunächst wird die Bedeutung der dort verwendeten Parameter erläutert. Weitergehende Informationen enthalten die Linux Drucker HOWTO und die manual pages zur Datei printcap.

cm - Kommentar
lp - Name des Gerätes, das für die Ausgabe geöffnet werden soll
sd - das sich auf dem lokalen Rechner befindende Spoolverzeichnis
     für den Drucker
af - die Datei für die Protokollierung der Druckerzugriffe
mx - die maximale Dateilaenge (Null bedeutet unbegrenzt)
if - Name des Skripts für den Eingabefilter

Und hier nun ein Auszug aus der /etc/printcap für den Drucker HP 5MP.

# /etc/printcap
#
# //zimmerman/oreilly über smbprint
#
lp:\
     :cm=HP 5MP Postscript OReilly an zimmerman:\
     :lp=/dev/lp1:\
     :sd=/var/spool/lpd/lp:\
     :af=/var/spool/lpd/lp/acct:\
     :mx#0:\
     :if=/usr/bin/smbprint:

Wichtig ist, daß man sich vergewissert, daß die Spool- und Accounting-Verzeichnisse existieren und für das Schreiben freigegeben sind.

Es muß sichergestellt werden, daß die if-Zeile den richtigen Pfad für das unten angegebene Skript smbprint enthält und daß dabei auf das richtige Ausgabegerät verwiesen wird, also die spezielle /dev-Datei.

Ausdruck der modifizierten Datei smbprint:

#!/bin/sh -x

# Dieses Skript ist ein Filter, der vom lp-Daemon auf-
# gerufen wird. Es benutzt das Programm smbclient, um 
# eine Datei auf dem angegebenen SMB-basierten Server 
# auszudrucken. Man könnte z.B. einen printcap Eintrag
# wie folgenden haben:
#
# smb:\
#   :lp=/dev/null:\
#   :sd=/usr/spool/smb:\
#   :sh:\
#   :if=/usr/local/samba/smbprint:
#
# Dieses würde einen Unix Drucker mit dem Namen "smb" 
# erzeugen, der über dieses Skript druckt. Hierfür muß
# das Spool-Verzeichnis /usr/spool/smb mit den passenden
# Rechten und Besitzern auf dem  eigenen System angelegt
# werden.

# In diesem Beispiel wird ein Windows PC mit dem Namen
# "lapland" verwendet, der einen Drucker unter dem
# Namen "printer" ohne Paßwort exportiert.
    
# Das Skript wurde von Michael Hamilton 
# (hamiltom@ecnz.co.nz) so verändert, daß der Server, 
# der Dienst und das Paßwort aus einer Datei
# /usr/var/spool/lpd/PRINTNAME/.config gelesen werden.
#
# Damit dieses funktioniert, muß der /etc/printcap 
# Eintrag eine Accounting Datei (af=) enthalten:
#
#   cdcolour:\
#       :cm=CD IBM Colorjet on 6th:\
#       :sd=/var/spool/lpd/cdcolour:\
#       :af=/var/spool/lpd/cdcolour/acct:\
#       :if=/usr/local/etc/smbprint:\
#       :mx=0:\
#       :lp=/dev/null:
#
# Die /usr/var/spool/lpd/PRINTNAME/.config Datei sollte
# enthalten:
#   server=PC_SERVER
#   service=PR_SHARENAME
#   password="Paßwort"
#
# z.B.:
#   server=PAULS_PC
#   service=CJET_371
#   password=""

#
# Logfile fürs Debugging, kann bei Bedarf in  /dev/null 
# geändert werden.
#
logfile=/tmp/smb-print.log
# logfile=/dev/null

#
# Der letzte Parameter für den Filter ist der Name der 
# Accounting Datei.
#
spool_dir=/var/spool/lpd/lp
config_file=$spool_dir/.config

# Liest die folgenden Variablen aus der Konfigurations-
# datei:
#   server
#   service
#   password
#   user
eval `cat $config_file`

#
# Hilfestellung fürs Debugging; um Platz zu sparen,
# kann ">>" in ">" geändert werden:
#
echo "server $server, service $service" >> $logfile

(
# Hinweis: Eventuell möchten Sie die Zeile "echo translate"
# hinzufügen, um eine automatische CR/LF Umsetzung beim
# Drucken durchzuführen.
      echo translate
      echo "print -"
      cat
) | /usr/bin/smbclient 
         "\\\\$server\\$service" $password -U $user \
                                 -N -P >> $logfile

Die meisten Linux-Distributionen enthalten nenscript, um ASCII-Dateien in PostScript zu konvertieren. Das folgende Perl-Skript gestaltet die Handhabung jedoch einfacher, in dem es eine einfache Schnittstelle zum Drucken unter Linux mit smbprint zur Verfügung stellt.

Syntax: print [-a|c|p] <dateiname>
        -a druckt <dateiname> als ASCII
        -c druckt <dateiname> formatiert als Source Code
        -p druckt <dateiname> als PostScript
         Wird kein Schalter gesetzt, so geht das Skript davon
         aus, daß es selbst den Dateityp ermitteln und
         die Datei dann entsprechend ausdrucken soll.

Wird smbprint genutzt, um ASCII-Dateien zu drucken, so werden zu lange Zeilen abgeschnitten. Falls es möglich ist, bricht das Skript dabei die zu langen Zeilen bei Leerzeichen und nicht in der Mitte eines Wortes um.

Die Formatierung des Quellcodes erfolgt mit nenscript. Es wird eine ASCII-Datei genommen und zweispaltig einschließlich Kopfzeile formatiert. Diese Kopfzeile enthält u.a. das Datum und den Dateinamen. Die Zeilen werden numeriert. Wenn man dieses Skript als Beispiel verwendet, lassen sich auch andere Formatierungen gestalten.

PostScript-Dokumente sind bereits passend formatiert, so daß sie direkt weitergeleitet werden können.

Und nun das Listing des Skriptes print:

#!/usr/bin/perl

# Skript:   print
# Autoren:  Brad Marshall, David Wood
#           Plugged In Communications
# Datum:    960808
#
# Skript, um auf "oreilly" zu drucken, welcher zur Zeit
# an "zimmerman" angeschlossen ist. Das Skript nimmt 
# verschiedene Typen von Dateien als Argumente an und
# bereitet sie entsprechend auf, um sie an ein Samba
# Druckskript zu übergeben.
#
# Zur Zeit werden folgende Formate unterstützt:
#
# ASCII      - Stellt sich, daß Zeilen, die länger als 
#              $line_length sind, bei einem Leerzeichen
#              umgebrochen werden.
# Postscript - Keine Veränderung notwendig.
# Code       - Wird mittels nenscript als Postscript 
#              formatiert, um richtig dargestellt zu 
#              werden (Hochformat, Font, etc.).
#

# Setzt die maximal erlaubte Länge einer Zeile bei 
# ASCII Text.
$line_length = 76;

# Setzt den Pfad und Namen des Druckskriptes von Samba
$print_prog = "/usr/bin/smbprint";

# Setzt den Pfad und Namen von nenscript 
# (dem ASCII nach PS Konverter)
$nenscript = "/usr/bin/nenscript";

unless ( -f $print_prog ) {
   die "Kann $print_prog nicht finden!";
}
unless ( -f $nenscript ) {
   die "Kann $nenscript nicht finden!";
}

&ParseCmdLine(@ARGV);

# DBG
print "Dateityp ist $filetype\n";

if ($filetype eq "ASCII") {
   &wrap($line_length);
} elsif ($filetype eq "code") {
   &codeformat;
} elsif ($filetype eq "ps") {
   &createarray;
} else {
   print "Unbekannter Dateityp.\n";
   exit 0;
}
# Daten an smbprint übergeben.
open(PRINTER, "|$print_prog") || 
   die "Kann  $print_prog nicht öffnen: $!\n";
foreach $line (@newlines) {
   print PRINTER $line;
}
# Ein zusätzlichen Zeilenvorschub senden, wenn die Datei 
# eine unvollständige letzte Zeile enthält.
print PRINTER "\n";
close(PRINTER);
print "Fertig\n";
exit 0;

# --------------------------------------------------- #
#       Alles weiter unten sind Unterfunktionen       #
# --------------------------------------------------- #
sub ParseCmdLine {
   # Kommandozeile parsen und den Dateityp der Datei
   # herausfinden

   # Läßt $arg und $file die Argumente, falls sie 
   # existieren, und den Dateinamen werden.
   if ($#_ < 0) {
      &usage;
   }
   # DBG
#    foreach $element (@_) {
#       print "*$element* \n";
#    }

   $arg = shift(@_);
   if ($arg =~ /\-./) {
      $cmd = $arg;
      # DBG
#     print "\$cmd gefunden.\n";

      $file = shift(@_);
   } else {
      $file = $arg;
   }

   # Den Dateityp definieren.
   unless ($cmd) {
      # Wir haben keine Argumente.

      if ($file =~ /\.ps$/) {
         $filetype = "ps";
      } elsif ($file =~ /\.java$|\.c$|\.h$|\.pl$|\.sh$|\.csh$|\.m4$|\.inc$|\.html$|\.htm$/) {
         $filetype = "code";
      } else {
         $filetype = "ASCII";
      }

   } else {
      # Der Typ ist in $arg angegeben.
      if ($cmd =~ /^-p$/) {
         $filetype = "ps";
      } elsif ($cmd =~ /^-c$/) {
         $filetype = "code";
      } elsif ($cmd =~ /^-a$/) {
         $filetype = "ASCII"
      }
   }
}

sub usage {
   print "
Syntax: print [-a|c|p] <dateiname>
        -a druckt <dateiname> als ASCII
        -c druckt <dateiname> formatiert als Source Code
        -p druckt <dateiname> als Postscript
         Wird kein Schalter gesetzt, so geht das Skript davon
         aus, daß es selbst den Dateityp ermitteln und
         die Datei dann entsprechend ausdrucken soll.
";
   exit(0);
}

sub wrap {
   # Erzeuge ein Array von Zeilen, wo jede Zeile weniger
   # Zeichen enthält als das angegebene Limit. Zu lange
   # Zeilen werden an Leerzeichen umgebrochen.

   # Limit der Zeichenanzahl pro Zeile besorgen
   $limit = pop(@_);

   # DBG
   #print "Zeilenumbruch Routine\n";
   #print "Das Limit für die Zeilenlänge ist $limit\n";

   # Datei lesen, parsen und in einem Array speichern.
   open(FILE, "<$file") || 
      die "Kann $file nicht öffnen: $!\n";
   while(<FILE>) {
      $line = $_;

      # DBG
      #print "Die Zeile ist:\n$line\n";

      # Zeile umbrechen, wenn sie über dem Limit ist.
      while ( length($line) > $limit ) {

         # DBG
         #print "Breche um...";

         # Die ersten $limit+1 Zeichen besorgen.
         $part = substr($line,0,$limit +1);

         # DBG
         #print "Der Teile der Zeile ist:\n$part\n";

         # Überprüfen, ob der letzte Buchstabe ein 
         # Leerzeichen ist.
         $last_char = substr($part,-1, 1);
         if ( " " eq $last_char ) {
            # Wenn ja, gib den Rest aus.

            # DBG
            #print "Der letzte Buchstabe war ein Leerzeichen\n";

            substr($line,0,$limit + 1) = "";
            substr($part,-1,1) = "";
            push(@newlines,"$part\n");
         } else {
            # Wenn nein, finde das letzte Leerzeichen in
            # dem Teil der Zeile und gib die Zeile bis 
            # dort aus.

            # DBG
            #print "Der letzte Buchstaben war kein Leerzeichen\n";
 
            # Entferne die Buchstaben nach $limit
            substr($part,-1,1) = "";
            # Kehre die Zeile um, um es leicht zu machen,
            # das letzte Leerzeichen zu finden.
            $revpart = reverse($part);
            $index = index($revpart," ");
            if ( $index > 0 ) {
               substr($line,0,$limit-$index) = "";
               push(@newlines,substr($part,0,$limit-$index)
                    . "\n");
            } else {
               # Da es kein Leerzeichen in der Zeile gab,
               # wird diese nur bis zum Limit ausgegeben.
               substr($line,0,$limit) = "";
               push(@newlines,substr($part,0,$limit)
                    . "\n");
            }
         }
      }
      push(@newlines,$line);
   }
   close(FILE);
}

sub codeformat {
   # Rufe die Funktion zum Zeilenumbruch auf.
   &wrap($line_length);

   # Schicke das Ergebnis durch nenscript, um eine
   # Postscript Datei zu erzeugen, die einige Ein-
   # stellung speziell für Source Code (Hochformat,
   # Courier Font, Zeilennummern) enthält. Drucke
   # diese zuerst in eine temporäre Datei.
   $tmpfile = "/tmp/nenscript$$";
   open(FILE, "|$nenscript -2G -i$file -N -p$tmpfile -r") ||
        die "Kann nenscript nicht öffnen: $!\n";
   foreach $line (@newlines) {
      print FILE $line;
   }
   close(FILE);

   # Lese die temporäre Datei wieder in ein Array ein,
   # so daß sie an das Druckskript von Samba übergeben
   # werden kann.
   @newlines = ("");
   open(FILE, "<$tmpfile") 
      || die "Kann $file nicht öffnen: $!\n";
   while(<FILE&>) {
      push(@newlines,$_);
   }
   close(FILE);
   system("rm $tmpfile");
}

sub createarray {
   # Erzeuge das Array für Postscript
   open(FILE, "<$file") 
      || die "Kann $file nicht öffnen: $!\n";
   while(<FILE>) {
      push(@newlines,$_);
   }
   close(FILE);
}


InhaltWeiter