SMB HOWTO: Доступ к принтеру Windows с машин работающих под Linux

Next Previous Contents

9. Доступ к принтеру Windows с машин работающих под Linux

Для доступа к принтеру на Windows машине, вы должны сделать следующее:

  1. ВЫ должны иметь правильные записи в файле /etc/printcap и они должны соответствовать локальной структуре директорий (для буферной директории, и т.п.)
  2. У вас должен быть скрипт /usr/bin/smbprint. Он поставляется вместе с исходными текстами Samba, но не со всеми двоичными дистрибутивами Samba. Его немного модифицированная копия обсуждается ниже.
  3. Если вы хотите преобразовывать ASCII файлы в Postscript, вы должны иметь программу nenscript, или ее эквивалент. nenscript -- это конвертер Postscript, он обычно устанавливается в директорию /usr/bin.
  4. Вы можете захотеть сделать печать через Samba более легкой, используя программы-надстройки. Простой скрипт на perl, который обрабатывает ASCII, Postscript или преобразованный Postscript приведен ниже.
  5. Вы также можете использовать MagicFilter для того, чтобы выполнить, описанное выше. Подробности о настройке MagicFilter приводятся ниже. MagicFilter имеет преимущества, потому, что он знает как автоматически преобразовывать достаточно большое количество форматов файлов.

Запись в файле /etc/printcap, приведенном ниже, сделана для принтера HP 5MP на сервере Windows NT. Используются следующие поля файла /etc/printcap:


        cm - комментарий
        lp - имя устройства, открываемого для вывода
        sd - директория спула принтера (на локальной машине)
        af - файл учета пользования принтером
        mx - максимальный размер файла (ноль -- без ограничений)
        if - имя входного фильтра (скрипта)

Для более детальной информации о печати смотрите Printing HOWTO или справочные страницы по printcap.


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

Убедитесь, что буферные директории и директория, используемая для учета пользования существуют и имеют право на запись. Убедитесь, что строка 'if' содержит правильный путь к скрипту smbprint (дан ниже) и убедитесь, что записи указывают на правильное устройство вывода (специальный файл /dev).

Далее идет сам скрипт smbprint. Он обычно находится в директории /usr/bin и написан Andrew Tridgell, человеком, который пакет создал Samba, насколько я знаю. Этот скрипт поставляется вместе с дистрибутивом исходного кода Samba, но отсутствует в некоторых бинарных дистрибутивах, так что я воссоздал его здесь.

Вы можете захотеть взглянуть на него более внимательно. Есть некоторые мелкие изменения, которые показали себя полезными.


#!/bin/sh -x

# Этот скрипт является входным фильтром для основанной на printcap
# печати на unix-машинах. Он использует программу smbclient для
# печати файла на указанный smb-сервер и сервис.
# Например вы можете иметь запись в printcap подобную этой
#
# smb:lp=/dev/null:sd=/usr/spool/smb:sh:if=/usr/local/samba/smbprint
#
# которая создает unix-принтер названный "smb", который будет
# печатать с помощью этого скрипта. Вам необходимо создать директорию
# спула /usr/spool/smb с соответствующими правами и владельцем

# Установите здесь сервер и сервис на который вы хотите печатать. В
# этом примере я имею PC с WfWg PC, названную "lapland", которая
# имеет экспортируемый принтер, называемый "printer" без пароля

#
# Далее скрипт был изменен hamiltom@ecnz.co.nz (Michael Hamilton)
# так что сервер, сервис и пароль могут быть считаны из файла
# /usr/var/spool/lpd/PRINTNAME/.config 
#
# Для того чтобы это работало запись в /etc/printcap должна
# включать файл учета использования (af=...):
#
#   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:
#
# Файл /usr/var/spool/lpd/PRINTNAME/.config должен содержать
#   server=PC_SERVER
#   service=PR_SHARENAME
#   password="password"
#
# Например,
#   server=PAULS_PC
#   service=CJET_371
#   password=""

#
# Debugging log file, change to /dev/null if you like.
#
logfile=/tmp/smb-print.log
# logfile=/dev/null


#
# The last parameter to the filter is the accounting file name.
#
spool_dir=/var/spool/lpd/lp
config_file=$spool_dir/.config

# Should read the following variables set in the config file:
#   server
#   service
#   password
#   user
eval `cat $config_file`

#
# Some debugging help, change the >> to > if you want to same space.
#
echo "server $server, service $service" >> $logfile

(
# NOTE You may wish to add the line `echo translate' if you want automatic
# CR/LF translation when printing.
        echo translate
        echo "print -"
        cat
) | /usr/bin/smbclient "\\\\$server\\$service" $password -U $user -N -P >> $logfile

Большинство дистрибутивов Linux поставляется с программой nenscript для преобразования ASCII документов в Postscript. Следующий скрипт на perl делает жизнь пользователя легче, обеспечивая простой интерфейс для печати используя smbprint.


Использование: print [-a|c|p] <filename>
       -a печатает <filename> как ASCII
       -c печатает <filename> отформатированный как исходный код
       -p печатает <filename> как Postscript
        Если опции не заданы, программа попробует определить
        тип файла и печатать соответственно

Используя smbprint для печати ASCII файлов, скрипт следит за длинными строками. Если возможно, этот скрипт разрывает длинную строку на пробеле (вместо разрыва в середине слова).

Форматирование исходного кода выполняется с помощью программы nenscript. Она берет ASCII-файл и форматирует его в 2 колонки с заголовком (дата, имя файла и т.п.). Эта программа также нумерует строки. Используя этот скрипт как пример, могут быть добавлены другие типы форматирования.

Postscript-документы являются уже отформатированы, так что они печатаются сразу.


#!/usr/bin/perl

# Скрипт:   print
# Авторы:   Brad Marshall, David Wood
#           Plugged In Communications
# Дата:   960808
#
# Используется для печати на сервис oreilly, который расположен на
# сервере zimmerman
# Назначение: Берет файлы разных типов как аргумент и обрабатывает
# их соответственно для передачи на скрипт печать Samba.
#
# В настоящее время поддерживаются типы файлов:
# 
# ASCII      - Если длина строки длиннее чем $line_length символов, то 
#              переносит строку на пробеле
# Postscript - Берет без обработки
# Code        - Форматирует в Postscript (используя nenscript), чтобы 
#               отображать правильно (альбомный формат, фонт и т.п.)
#

# Установить максимальную длину строки ASCII текста
$line_length = 76;

# Установить путь к скрипту печати Samba
$print_prog = "/usr/bin/smbprint";

# Установить путь и имя nenscript (конвертера ASCII-->Postscript)
$nenscript = "/usr/bin/nenscript";

unless ( -f $print_prog ) {
        die "Can't find $print_prog!";
}
unless ( -f $nenscript ) {
        die "Can't find $nenscript!";
}

&ParseCmdLine(@ARGV);

# DBG
print "filetype is $filetype\n";

if ($filetype eq "ASCII") {
        &wrap($line_length);
} elsif ($filetype eq "code") {
        &codeformat;
} elsif ($filetype eq "ps") {
        &createarray;
} else {
        print "Sorry..no known file type.\n";
        exit 0;
}
# Pipe the array to smbprint
open(PRINTER, "|$print_prog") || die "Can't open $print_prog: $!\n";
foreach $line (@newlines) {
        print PRINTER $line;
}
# Send an extra linefeed in case a file has an incomplete last line.
print PRINTER "\n";
close(PRINTER);
print "Completed\n";
exit 0;

# --------------------------------------------------- #
#        Everything below here is a subroutine        #
# --------------------------------------------------- #

sub ParseCmdLine {
        # Parses the command line, finding out what file type the file is

        # Gets $arg and $file to be the arguments (if the exists)
        # and the filename
        if ($#_ < 0) {
                &usage;
        }
        # DBG
#       foreach $element (@_) {
#               print "*$element* \n";
#       }

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

                $file = shift(@_);
        } else {
                $file = $arg;
        }
        
        # Defining the file type
        unless ($cmd) {
                # We have no arguments

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

                # Process $file for what type is it and return $filetype 
        } else {
                # We have what type it is in $arg
                if ($cmd =~ /^-p$/) {
                        $filetype = "ps";
                } elsif ($cmd =~ /^-c$/) {
                        $filetype = "code";
                } elsif ($cmd =~ /^-a$/) {
                        $filetype = "ASCII"
                }
        }
}

sub usage {
        print "
Использование: print [-a|c|p] <filename>
       -a печатает <filename> как ASCII
       -c печатает <filename> отформатированный как исходный код
       -p печатает <filename> как Postscript
        Если опции не заданы, программа попробует определить
        тип файла и печатать соответственно\n
";
        exit(0);
}

sub wrap {
        # Create an array of file lines, where each line is < the 
        # number of characters specified, and wrapped only on whitespace

        # Get the number of characters to limit the line to.
        $limit = pop(@_);

        # DBG
        #print "Entering subroutine wrap\n";
        #print "The line length limit is $limit\n";

        # Read in the file, parse and put into an array.
        open(FILE, "<$file") || die "Can't open $file: $!\n";
        while(<FILE>) {
                $line = $_;
                
                # DBG
                #print "The line is:\n$line\n";

                # Wrap the line if it is over the limit.
                while ( length($line) > $limit ) {
                        
                        # DBG
                        #print "Wrapping...";

                        # Get the first $limit +1 characters.
                        $part = substr($line,0,$limit +1);

                        # DBG
                        #print "The partial line is:\n$part\n";

                        # Check to see if the last character is a space.
                        $last_char = substr($part,-1, 1);
                        if ( " " eq $last_char ) {
                            # If it is, print the rest.

                            # DBG
                            #print "The last character was a space\n";

                            substr($line,0,$limit + 1) = "";
                            substr($part,-1,1) = "";
                            push(@newlines,"$part\n");
                        } else {
                             # If it is not, find the last space in the 
                             # sub-line and print up to there.

                            # DBG
                            #print "The last character was not a space\n";

                             # Remove the character past $limit
                             substr($part,-1,1) = "";
                             # Reverse the line to make it easy to find
                             # the last space.
                             $revpart = reverse($part);
                             $index = index($revpart," ");
                             if ( $index > 0 ) {
                               substr($line,0,$limit-$index) = "";
                               push(@newlines,substr($part,0,$limit-$index) 
                                   . "\n");
                             } else {
                               # There was no space in the line, so
                               # print it up to $limit.
                               substr($line,0,$limit) = "";
                               push(@newlines,substr($part,0,$limit) 
                                   . "\n");
                             }
                        }
                }
                push(@newlines,$line);
        }
        close(FILE);
}

sub codeformat {
        # Call subroutine wrap then filter through nenscript
        &wrap($line_length);
        
        # Pipe the results through nenscript to create a Postscript
        # file that adheres to some decent format for printing
        # source code (landscape, Courier font, line numbers).
        # Print this to a temporary file first.
        $tmpfile = "/tmp/nenscript$$";
        open(FILE, "|$nenscript -2G -i$file -N -p$tmpfile -r") || 
                die "Can't open nenscript: $!\n";
        foreach $line (@newlines) {
                print FILE $line;
        }
        close(FILE);
        
        # Read the temporary file back into an array so it can be
        # passed to the Samba print script.
        @newlines = ("");
        open(FILE, "<$tmpfile") || die "Can't open $file: $!\n";
        while(<FILE>) {
                push(@newlines,$_);
        }
        close(FILE);
        system("rm $tmpfile");
}

sub createarray {
        # Create the array for postscript
        open(FILE, "<$file") || die "Can't open $file: $!\n";
        while(<FILE>) {
                push(@newlines,$_);
        }
        close(FILE);
}

Теперь о применении MagicFilter. Спасибо Alberto Menegazzi ( flash.egon@iol.it) за его информацию.

Alberto сообщил: -------------------------------------------------------------- 1) Установите MagicFilter в /usr/bin/local с фильтрами для необходимых принтеров, но НЕ заполняйте записи в /etc/printcap, как предполагается в документации на MagicFilter.

2) Запишите в /etc/printcap примерно вот такую запись (Это сделано для моего принтера LaserJet 4L):

lp|ljet4l:\ :cm=HP LaserJet 4L:\ :lp=/dev/null:\ # or /dev/lp1 :sd=/var/spool/lpd/ljet4l:\ :af=/var/spool/lpd/ljet4l/acct:\ :sh:mx#0:\ :if=/usr/local/bin/main-filter:

Вы должны, объяснить, что устройство lp=/dev/... открывается для блокирования, так что для каждого удаленного принтера используется одно "виртуальное устройство".

Пример создания : touch /dev/ljet4l

3) Напишите фильтр /usr/local/bin/main-filter, с таким же образом предполагая использование ljet4l-filter вместо cat.

Вот так для меня.

#! /bin/sh logfile=/var/log/smb-print.log spool_dir=/var/spool/lpd/ljet4l ( echo "print -" /usr/local/bin/ljet4l-filter ) | /usr/bin/smbclient "\\\\SHIR\\HPLJ4" -N -P >> $logfile

P.S. : Это цитата из Print2Win mini-Howto о блокировании, а также о том, зачем создавать виртуальные принтера

---Начало здесь --------- Совет от Rick Bressler :

Хороший совет. Я использую нечто подобное. Вот один полезный совет, хотя он не является хорошей идеей:

:lp=/dev/null:\

lpr делает 'монопольное (exclusive)' открытие файла, который вы укажете в поле lp=. Он делает это для предотвращения попыток множества процессов печатать одновременно на одном и том же принтере.

Побочным эффектом этого, в вашем случае является то, что eng и colour не могут печатать одновременно, (обычно более или менее прозрачно, поскольку они вероятно печатают быстро и поскольку вы не замечаете, что они ставят задания в очередь), но любые другие процессы, которые пытаются записать в /dev/null не будут работать!

На однопользовательской системе, это вероятно не является большой проблемой. У меня имеется система с 50 принтерами. В этом случае это может быть проблемой.

Решение этой проблемы заключалось в создании устройства для каждого их принтеров. Например: touch /dev/eng.

Я модифицировал записи lp в файле printcap, приведенном выше, приняв во внимание пожелания Rick. Я сделал следующее:

#touch /dev/eng #touch /dev/colour

---Конец -----

--------------------------------------------------------------


Next Previous Contents