下一页 上一页 目录

10. 与 Linux 机器共享 Windows 打印机

要在 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 的详细信息在 perl 脚本下方给出。 MagicFilter 具有优势,因为它知道如何自动转换多种文件格式。

下面的 /etc/printcap 条目适用于 Windows NT 主机上的 HP 5MP 打印机。 条目如下


        cm - comment
        lp - device name to open for output
        sd - the printer's spool directory (on the local machine)
        af - the accounting file
        mx - the maximum file size (zero is unlimited)
        if - name of the input filter (script)

有关更多信息,请参阅 Printing HOWTO 或 printcap 的 man 手册。


# /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 中,并且据我所知,归功于 Samba 的创建者 Andrew Tridgell。 它随 Samba 源代码发行版一起提供,但在某些二进制发行版中不存在,因此我在此处重新创建了它。

您可能希望仔细查看此内容。 有一些小的改动已被证明是有用的。


#!/bin/sh -x

# This script is an input filter for printcap printing on a unix machine. It
# uses the smbclient program to print the file to the specified smb-based 
# server and service.
# For example you could have a printcap entry like this
#
# smb:lp=/dev/null:sd=/usr/spool/smb:sh:if=/usr/local/samba/smbprint
#
# which would create a unix printer called "smb" that will print via this 
# script. You will need to create the spool directory /usr/spool/smb with
# appropriate permissions and ownerships for your system.

# Set these to the server and service you wish to print to 
# In this example I have a WfWg PC called "lapland" that has a printer 
# exported called "printer" with no password.

#
# Script further altered by hamiltom@ecnz.co.nz (Michael Hamilton)
# so that the server, service, and password can be read from 
# a /usr/var/spool/lpd/PRINTNAME/.config file.
#
# In order for this to work the /etc/printcap entry must include an 
# accounting file (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:
#
# The /usr/var/spool/lpd/PRINTNAME/.config file should contain:
#   server=PC_SERVER
#   service=PR_SHARENAME
#   password="password"
#
# E.g.
#   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 进行 Linux 打印提供一个简单的接口,使生活更加轻松。


Usage: print [-a|c|p] <filename>
       -a prints <filename> as ASCII
       -c prints <filename> formatted as source code
       -p prints <filename> as Postscript
        If no switch is given, print attempts to
        guess the file type and print appropriately.

使用 smbprint 打印 ASCII 文件往往会截断长行。 如果可能,此脚本会在空白处(而不是在单词中间)断开长行。

源代码格式化是使用 nenscript 完成的。 它接受一个 ASCII 文件,并将其格式化为 2 列,带有花哨的标题(日期、文件名等)。 它还对行进行编号。 以此为例,可以完成其他类型的格式化。

Postscript 文档已经正确格式化,因此它们可以直接通过。


#!/usr/bin/perl

# Script:   print
# Authors:  Brad Marshall, David Wood
#           Plugged In Communications
# Date:     960808
#
# Script to print to a Postscript printer via Samba.
# Purpose:  Takes files of various types as arguments and 
# processes them appropriately for piping to a Samba print script.
#
# Currently supported file types:
# 
# ASCII      - ensures that lines longer than $line_length characters wrap on
#              whitespace.
# Postscript - Takes no action.
# Code       - Formats in Postscript (using nenscript) to display
#              properly (landscape, font, etc).
#

# Set the maximum allowable length for each line of ASCII text.
$line_length = 76;

# Set the path and name of the Samba print script
$print_prog = "/usr/bin/smbprint";

# Set the path and name to nenscript (the ASCII-->Postscript converter)
$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 "
Usage: print [-a|c|p] <filename>
       -a prints <filename> as ASCII
       -c prints <filename> formatted as source code
       -p prints <filename> as Postscript
        If no switch is given, print attempts to
        guess the file type and print appropriately.\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 中安装您需要的打印机过滤器,但不要按照 MagicFilter 文档中的建议填充 /etc/printcap。

2) 像这样编写 /etc/printcap(这是为我的 LaserJet 4L 完成的)

lp|ljet4l:\ :cm=HP LaserJet 4L:\ :lp=/dev/null:\ # 或 /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

附注:以下是关于锁定以及为什么要创建虚拟打印机的 Print2Win mini-Howto 中的引述

---从这里开始

来自 Rick Bressler 的提示

很好的技巧表。 我使用非常相似的东西。 一个有用的提示,这不是一个特别好的主意

:lp=/dev/null:\

lpr 对您指定为 lp= 的文件执行“独占”打开。 这样做是为了防止多个进程同时尝试打印到同一台打印机。

这样做的副作用是,在您的情况下,eng 和 colour 无法同时打印(通常或多或少是透明的,因为它们可能打印很快,并且由于它们排队,您可能不会注意到),但是任何其他尝试写入 /dev/null 的进程都会崩溃!

在单用户系统上,可能不是大问题。 我有一个拥有 50 多台打印机的系统。 在那里这将是一个问题。

解决方案是为每个打印机创建一个虚拟打印机。 例如:touch /dev/eng。

我已经修改了上面 printcap 文件中的 lp 条目,以考虑 Rick 的建议。 我做了以下操作

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

---到此结束

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


下一页 上一页 目录