APLawrence.com -  Resources for Unix and Linux Systems, Bloggers and the self-employed

© May 2019 Anthony Lawrence

CUPS (Common Unix Printing System) print to file - the hard way!

Sometimes what should be simple isn't. I wanted a CUPS printer that would just print to a file. Actually, I wanted a bit more than that, but printing to a file was a reasonable start.

What I really wanted to be able to do was examine a print job and then, based on its content, put it into a particular file or perhaps send it on its way somewhere else. The reason for this is that a "printer" is often a convenient and easy abstraction that some other program can use - whether or not that program is on this machine or not. You can do (and I have done) similar things with special email addresses, but printers are often easier (see also Using System V interface scripts with CUPS printing).

So..although the ultimate place this printer would live would be on a RedHat 8.0 system, I initially attempted to configure it on my Mac OS X machine. Why? Well, because my main RedHat machine is a 7.2 box that doesn't use CUPS, and although I have an 8.0 machine, it's dual boot and at the moment it is tied up with a project I'm doing on one of its other OSes. I usually have more than one thing going on at the same time, and it would have been mildly annoying to keep rebooting that as I switched gears. Besides, Mac OS X uses CUPS, so why not?

Awful Docs

Small gripe here: aside from the problems I ran into on this, I do like CUPS. It's very flexible, I think it combines the best of SysV and BSD style printing, and I'm sure it will gain ground quickly. However: the documentation bites. Unix documentation is well known for its assumption of prior knowledge, but the CUPS docs take that to a whole new level. In my search for answers to what seemed like simple questions, I kept running into stone walls that had tantalizing hints, but very little real help. The "HowTo" reached from https://www.cups.org is mostly a long winded treatise advising you to read the manual before you dare ask a question on the newsgroup. The "Overview" isn't much help, and either is the FAQ. All very disappointing.

When I have a better handle on this myself I intend to write up something of my own.

First Attempt

One of the really nice things about CUPS is its browser interface. Point your browser at localhost:631 and you have pretty good control of CUPS. It isn't quite as good as you can get at the command line, but it's certainly a heck of a lot better than, for example, what Apple supplies to define and manage printers.

The browser interface certainly makes it seem to be possible to create a simple printer. It even shows the syntax you should use for printing to a file:


However, if you actually try to use such syntax, it complains "client-error-not-possible". The same thing happens if you try to use lpadmin,

bash-2.05a$ sudo lpadmin -p fprint -E -v /tmp/xx  -m raw
lpadmin: add-printer failed: client-error-not-possible

and if you actually manually modify /etc/cups/printers.conf the printer just doesn't work.

Of course, I really didn't want to print to a file anyway. I really want to print to a script. The easy way to do that on System V is to print to a named pipe, and have a program that constantly reads that data, passing it on to a program. Well, it was beginning to look like this just wasn't going to happen with CUPS.

A Socket Printer

I had already added my Netgear print server to CUPS. That was easy:

sudo lpadmin -p laserjet6L -E -v socket:// -m laserjet

Since CUPS can very easily print to a network socket, I added a new printer:

sudo lpadmin -p tofile -E -v socket://localhost:12000 -m raw

Now all I needed was a server listening on port 12000. That's easy enough in Perl:

use IO::Socket::INET;
$pserve=IO::Socket::INET->new(LocalPort => $myport,Type=>SOCK_STREAM,Reuse=>1,Listen=>1) or die "can't do that $!\n";
while ($pjob=$pserve->accept()) {
  open(J,">>/private/tmp/x") or print "having issues $!\n";
  print J "New job...\n";
  while (<$pjob>) {
  print J "$_";
  close J;
  close $pjob;

Obviously this is just the shell of what I really want, but it is the basic functionality: anything printed to "tofile" ends up in /tmp/x All I need to do now is write more code, which can be incorporated here or spun off to other programs.

Got something to add? Send me email.

(OLDER)    <- More Stuff -> (NEWER)    (NEWEST)   

Printer Friendly Version

-> CUPS (Common Unix Printing System) print to file - the hard


Inexpensive and informative Apple related e-books:

Sierra: A Take Control Crash Course

Take Control of Automating Your Mac

Take Control of iCloud, Fifth Edition

Take Control of IOS 11

Are Your Bits Flipped?

More Articles by © Tony Lawrence

Related Articles

1. Create a new printer using the Printer Setup Utility. Give it the following characteristics:
- "IP Printing"
- Printer Type: "Socket/HP Jet Direct"
- Printer Address: "localhost:12000"
- Queue Name: "localhost:12000"
- Printer Model: "Apple"
- Model Name: "Apple Color LaserWriter 12/600 PS v2014.108"

2. Use the lpadmin utility to fix the URL, since the one the utility creates will be incorrect:
- lpadmin -p localhost:12000 -v socket://localhost:12000/

The jobs coming into the perl script will now be very high quality postscript, easily converted to PDF using any of the standard methods and look much better than the other PDFs did for me.

Thanks for the tip!

MacOSXMacosxcupstofile :

The "client-error-not-possible" when you attempt to add a file printer can possibly be avoided by enabling file printers in cupsd.conf or similar. This enables the printer to be set up and printed to. But mine never actually creates or appends to the specified file, sigh!

This MAY clarify the problem (or not)... an excerpt from the cups manual.



FileDevice Yes

FileDevice No


The FileDevice directive determines whether the scheduler allows new printers to be added using device URIs of the form file:/filename. File devices are most often used to test new printer drivers and do no support raw file printing.

The default setting is No.

File devices are managed by the scheduler. Since the scheduler normally runs as the root user, file devices can be used to overwrite system files and potentially gain unauthorized access to the system. If you must create printers using file devices, we recommend that you set the FileDevice directive to Yes for only as long as you need to add the printers to the system, and then reset the directive to No.

Mon Mar 14 21:23:12 2005: 178   TonyLawrence

Turns out that the easy way to do this is to edit /etc/cups/printers.conf and fix the line with the device to be whatever you want, but tell cups it's parallel:

DeviceURI parallel:/tmp/whatever

Restart cups and all is happy..

To add it that way to start with, use
lpadmin -p printername  -E -v parallel:/dev/whatever

Tue Mar 29 14:59:15 2005: 244   anonymous

The parallel way is the best for me, but remember that you must create first
the file,

> /tmp/myfile.prn
chmod a+rw /tmp/myfile.prn
and if cupsd is running with lp user then
chown lp:lp /tmp/myfile.prn

I miss LPRng. (may be better than cups??!)

Wed Jul 27 23:12:22 2005: 887   tlambert

All you need to do is add a new backend type "file" (run these commands as root):

cd /usr/libexec/cups/backend
cat > file
# Stupid printer backend that just appends to the file "fee" in /tmp
cat >> /tmp/fee

Then you have to make it executable:

chmod 755 file

Now you want to add a printer that uses this:

cd /etc/cups
cat >> printers.conf
<Printer foofile>
Info foofile
DeviceURI file:/tmp/doesntmatter
State Idle
Accepting Yes
Shared Yes
JobSheets none none
QuotaPeriod 0
PageLimit 0
KLimit 0

Next, kick the cupsd:

killall -1 cupsd

Verify that it's there:

lpstat -p foofile
printer foofile is idle. enabled since Jan 01 00:00

Obviously, you can make the backend do whatever you want it to do. You could also pass it a file name as arguments using "job title" or whatever that CUPS passes to a backend.

-- Terry

Wed Jul 27 23:17:34 2005: 888   tlambert

Other examples live here:


Thu Jul 28 10:57:22 2005: 892   TonyLawrence

Yes, thank you. There's a reason why "The Hard Way" was part of the title: Cups has very poor documentation and I hadn't grokked the backend concept when I needed this feature.

There are several other articles here dealing with Cups, but I sure wouldn't mind seeing more. It's a good system, but suffers a little from "too much power". You can, of course, bypass all that confusion by using SysV style interface scripts ( (link) ) when you don't need all the other stuff.

Sat Oct 15 22:12:17 2005: 1207   anonymous

You can use netcat as a socket listener then spool the printer output to the socket on which netcat is listening.

nc -l -p9100 >filename.out

I have found netcat to be very useful in capuring and redirecting print output streams.

Wed Jun 14 14:00:37 2006: 2113   YDurant

you could add FileDevice YES to cupsd.conf to allow printing to a file from cups.

Wed Jun 14 14:11:15 2006: 2114   TonyLawrence

Yeah: that's all discussed above.

Wed Jul 2 22:02:34 2008: 4388   SimonBunker

Thanks for writing this. Particularly useful was finding out about the backend filters. They really do make this sort of thing easy. I have used this to create a render farm for rendering 3d animations - the details are here: (link)

Fri Oct 30 17:59:12 2009: 7386   Boborama

I was able to use these tips to create a "print to file" that I tested with "lp -d printername testfile" It does create my "file" in the tmp directory but won't "append" to build a continuous stream. I need a "printer" that just builds a never ending stream. I built the dummy "backend device" which just doesn't seem to work for me. Best I can seem to get is every time I print... the file is overwritten with the new job.

Fri Oct 30 18:10:39 2009: 7387   TonyLawrence

If you used ">>", it should be appending.

Sat Oct 31 02:42:54 2009: 7394   boborama

Yes, I understand UNIX fairly well. When I execute the backend script from the command line and type some text ^D to exit. It DOES get appended to my tmp file. I just can't seem to get any results when I set the DeviceURI = to the backend script. The Word "file:" seems to work even though I have no backend script called "file" (works with the overwrite behavior). If I supply a nonexistant backend name like "gibberish" checking the status of the printer tells me that the backend does not exist. When I change it to my custom backend... it reports that it's idle and enabled. Printing to it produces nothing. maybe I'll change the backend to "echo hello world" and see if that works. Thanks for the feedback.

Sat Oct 31 03:12:47 2009: 7395   boborama

I changed my backend to append an echo of "hello world" and that worked fine. It would seem then that the backend is indeed executing...but it's not "receiving" a stream of text for "cat >>" to act upon. I changed the script to echo out the incoming parameters from CUPS and received some nice output "114 reb test2 1 document-name=test2 finishings=3 number-up=1 job-uuid=urn:uuid:78d63f83-7e4a-3dc1-605b-cd4b77488939 /private/var/spool/cups/d00114-001" basically the job number and file name etc. Maybe I need to grab the parameters and cat with append what appears to be the spool job path. Don't get me wrong your article has saved me a ton of time pouring over and deciphering a CUPS manual for what ought to be a simple thing to do. Many thanks.

Sat Oct 31 11:12:07 2009: 7396   TonyLawrence


This is not a backend script. This is a socket printer with a background script listening at the port.


Printer Friendly Version

Related Articles

Have you tried Searching this site?

This is a Unix/Linux resource website. It contains technical articles about Unix, Linux and general computing related subjects, opinion, news, help files, how-to's, tutorials and more.

Contact us

Printer Friendly Version

If debugging is the process of removing bugs, then programming must be the process of putting them in. (Edsger W. Dijkstra)

Linux posts

Troubleshooting posts

This post tagged:









Unix/Linux Consultants

Skills Tests

Unix/Linux Book Reviews

My Unix/Linux Troubleshooting Book

This site runs on Linode