PLAN 9 DESKTOP GUIDE
[bunny_whit]
INDEX
• What is Plan 9?
• Limitations and Workarounds
□ Connecting to Other Systems
☆ VNC
☆ RDP
☆ SSH
☆ 9P
☆ Other methods
□ Porting Applications
□ Emulating other Operating Systems
□ Virtualizing other Operating Systems
• Basics
□ Window Management
□ Copy Pasting
□ Essential Programs
□ Manipulating Text in the Terminal
□ Acme - The Do It All Application
□ Multiple Workspaces
□ Tiling Windows
□ Plumbing
• System Administration
□ Basic System Administration
□ Battery Monitoring
□ Configuring Startup and Shutdown
□ Wallpapers, themes, etc...
□ Internationalization
□ User Management and Security
□ Disk Management
□ Backups
□ Package Management
□ File Management
□ Tips for UNIX Sysadmins
□ Digression on find and locate
□ Quick CPU+AUTH+Qemu+Drawterm HOWTO
☆ 9front
☆ 9legacy
☆ CPU+Rio desktop
☆ CPU+PXE terminals
• Automation
□ Shell Scripting
□ Rio Scripting
☆ Scrambling and Unscrambling a Rio Screen
☆ max - Maximizing Windows
☆ ws - Multiple Workspaces
☆ tile - Tiling Window Manager
□ Acme Scripting
☆ Coffee - Chill ASCII Animations
☆ Slides - Acme Presentations
☆ Chat - Simple Peer to Peer Chatting
☆ Play - Music Playlist in Acme
□ Web Scripting
☆ 9front Web Scripts
• Development
□ Version Control
□ Files and Namespaces
• The Web
□ Wireless Network
□ Browsing The Web
□ Downloading
□ Chatting
□ Running a Web Server
• Multimedia
□ Audio
□ Video
☆ Youtube
• Graphics
□ Viewing Images/Documents
☆ Reading Comics
□ Creating Images
□ Taking a Screenshot
☆ Screencasting
• Peripherals
□ USB sticks
□ CD/DVD/BS's
□ Printers
• Games and other Fun Stuff
□ Included Games
□ Included Game Emulators
□ 3rd Party Games
□ Edutainment
☆ Arithmetic
☆ Quiz
☆ Touchtype
□ Playing With Telnet
□ Miscellaneous Fun
□ Obscure Operating Systems
☆ Inferno
☆ UNIX V8
• Office
□ Reading Office Documents
☆ Reading Epubs
□ Writing Office Documents
☆ Tweaking Troff Macros
□ Spellchecking
□ PIM
☆ 2do Lists
☆ Queues
☆ Password Management
☆ Personal Accounting
☆ Time Management
□ Math, Graphs and Units
□ Spreadsheets
□ Databases
☆ Awk as a Database
☆ Ndb as a Database
• Conclusion
What is Plan 9..?
Briefly, Plan 9 from Bell Labs is a computer operating system developed by the
original UNIX design team. After decades of work on Research UNIX in the late
80's, the team decided to write a new operating system from scratch, Plan 9 was
finally released in 1992, and a few years later they released yet another
operating system called Inferno, which share many of the same characteristics
as its sister project. These systems, and variations thereof, have more or less
been in continual development since. The history and design philosophy behind
these operating systems, is interesting, but we will not talk about that here.
Instead, this article will focus on the practical aspects of using Plan 9 as
day-to-day desktop system.
Beware that prior exposure to UNIX is a double-edged sword. There are similar
sounding commands and conventions between the two platforms, and Plan 9 does
follow the UNIX philosophy (much more so then UNIX in fact). Nevertheless, Plan
9 is not UNIX! It is an operating system written entirely from scratch,
backwards compatibility was not a goal. If you expect just another Ubuntu
spin-off, you will be very disappointed. In fact, lets be clear here: You will
be disappointed, period. Now with that disclaimer out of the way, lets have
some fun!
In 2002 the 4th edition of Plan 9 was released, it was essentially a rolling
release, that continued to receive updated from Bell Labs until 2015, when the
project was officially discontinued. In mid 2021 though, Bell Labs gave
ownership of all previous Plan 9 sources to the Plan 9 foundation. The goal of
this foundation is to continue the development of Plan 9, but so far, not much
has happened. There are several community forks around though, two of them,
9legacy and 9front, sprang into existence around 2010. If you want to use Plan
9 as a day-to-day desktop, which will be the focus of this article, I strongly
recommend going with 9front. It is likely the only candidate that will actually
run on your physical hardware, and it has many features that a modern user
takes for granted, such as auto-mounting USB sticks, wifi support, working
audio, video playback and git. 9front has an excellent fqa and community wiki,
that do a far better job of presenting accurate information then I do (be
prepared for quirky humor though!). Still, it can be interesting to play with
9legacy too, if only for historical curiosity, so I will give some pointers in
this article on "classic Plan 9" (9legacy and the old 4th edition of Plan 9 are
nearly identical), where it differs significantly from 9front. For classic Plan
9, the Plan 9 wiki from Bell Labs is a better source of documentation then the
9front resources.
Limitations and workarounds
[we_will_sh]
More then anything, Plan 9 is a simple operating system. The kernel is only
200,000 lines of code, and the userland about a million. In comparison the
source code for the Firefox web browser is more than 24 million lines of code!
As you might imagine then, there are no "modern" web browsers in Plan 9. There
are no office suits, triple A games, VOIP or repositories of 30,000
pre-compiled packages. Plan 9 is not for the faint of heart!
Of course there are workarounds for the above limitations, here are a few
suggestions:
Connecting to Other Systems
VNC
It is simple enough to connect to a remote UNIX/Windows machine from Plan 9
using VNC, or vice versa (I use the term "UNIX" broadly - it includes Mac,
Android, Linux, BSD, etc...). From Plan 9 you can connect to a VNC server using
vncv, or run a VNC server with vncs (there is little reason to run a VNC server
on Plan 9 though, use drawterm, mentioned below, instead).
For example, assuming you have tigervnc installed on a UNIX machine, with the
ip address 192.168.0.1, and a desired VNC screen resolution of 1366 x 768
pixels: You can run vncserver -geometry 1366x768 :1, and give it a login
password (if you are not prompted for a password you may need to run vncpasswd
first). Now, on the Plan 9 machine, run the command vncv 192.168.0.1:1, and
login. By default this will probably run a very basic twm desktop, which makes
many inexperienced users suspect that the desktop failed somehow. You probably
want to change ~/.vnc/xstartup, to run a fancier window manager. To use openbox
instead of twm for instance, add this line to the file:
exec /usr/bin/openbox-session
You can choose whatever desktop you want here, but beware that configuring
xstartup gets vastly more complex if you use some bloated mess like GNOME or
KDE.
RDP
It is possible to connect to a remote Windows machine using RDP, see rd if you
need that sort of thing.
SSH
9front ships with a working ssh and sshfs client (sshfs mounts the remote file
system in /n/ssh), but classic Plan 9 has a very outdated version of ssh, that
in all likelihood will not (or at least should not) be able to connect to your
UNIX machines.
9P
It is in fact much easier to import Plan 9 technologies to foreign systems then
vice versa, and there are good solutions for working with Plan 9 from UNIX. We
will discuss technologies such as plan9port and drawterm later, but for now,
lets talk about mounting the Plan 9 file system natively in UNIX using the 9P
protocol. There are various ways you can do this, including mounting it
directly, in Linux at least, like so: sudo mount -o rw -t 9p 192.168.0.1 /mnt
(substitute the ip address for the Plan 9 machine you're using). But you will
probably get better results using one of the many 9P clients that's out there,
such as 9pfuse from the plan9port package, or 9pfs You can use it like so: 9pfs
192.168.0.1 /mnt, assuming you have the right privileges.
Other methods
There is some support for NFS and SMB in Plan 9 (see nfs(4) and cifs(4)), but I
don't recommend using NFS, the Plan 9 client is very outdated. Speaking of
outdated, you naturally have ftpfs and telnet as well.
Porting applications
There exists a fairly good port of Plan 9 userland programs and services for
UNIX, called Plan9Port (or p9p for short - a more lightweight alternative is
9base), it is available in the repositories of most popular UNIX systems. Once
installed, use the 9 command to run the Plan9Port programs rather then the UNIX
counterparts, eg. 9 acme. It does not fully replicate the Plan 9 experience of
course, but it does make UNIX less of a pain to use.
To run a full Plan 9 shell, using Plan9Port commands instead of the UNIX
equivalents, either run 9 acme, execute win in it and run 9 rc. Or run 9 9term,
then run 9 rc. You can configure your ~/.xinitrc file to start the Plan 9
look-alike window manager, with exec 9 rio, and set up a very authentic looking
Plan 9 desktop. But there is little point in doing so, unless you really want
to hide the fact that UNIX is running in the background. Plan9Port's rio only
looks like the Plan 9 window manager, but it doesn't have the same useful
features, and it is quite flaky to boot. In my opinion there are far better
native UNIX alternatives, including the Plan9 inspired wmii/dwm window
managers, or variations thereof.
It is possible, but much harder, to go in the other direction. Plan 9 has a
UNIX compatibility suit of programs and libraries in /bin/ape, such as ape/sh,
which gives you a ksh like UNIX shell (run vt first to emulate a VT-100
terminal). And ape/cc a POSIX compliant C compiler, with corresponding
UNIX-friendly libraries. Plus a few other UNIX'y utilities. This UNIX
compatibility is old and quite unmaintained. 9front has its own semi-official
portability layer called npe, see the 9front porting guide for further tips.
Note however that simply having a UNIX shell, does not mean that all your shell
scripts will magically work. Plan 9 has it's own version of cat, echo, ls, sed
and so on. If your script uses these programs, it needs to be adapted to use
the Plan 9 versions of them. As always, read the man pages carefully (no really
- read them!).
Finally, even though Plan 9 [S:has:S] had a very good POSIX compliance, it's by
no means certain that UNIX programs will compile. Most will not. The majority
of UNIX software does not restrict themselves to POSIX alone, but require large
extensions. Most of which are not supported. For example, Plan 9 does not have
X (by default), curses, sockets, numerical UID/GID's or links, so any programs
depending on such things needs to be patched and rewritten before they will
work. In practice only the simplest of programs can be ported with any
reasonable amount of effort.
Emulating other operating systems
[emulation]
In a traditional Plan 9 network, one or more CPU servers are providing file and
authentication services to multiple diskless workstations, called "terminals".
These terminals are desktop systems connected to the CPU server. This is a bit
confusing for UNIX users, so in this article we will refer to a diskless
workstation as a remote desktop, and a window running a shell as a terminal, as
is the custom in UNIX. If you have set up a CPU Server in Plan 9 (see section
7.5 and 7.6 in the 9front fqa - see also Quick CPU+AUTH+Qemu+Drawterm HOWTO
below), either physically, or on a virtual machine, you can emulate a Plan 9
remote desktop on a UNIX/Windows machine with drawterm (for classic Plan 9 use
this link). drawterm works very well, it also has access to the host file
system under /mnt/term, making it easy to work on files across operating
systems.
There is a 3rd party port of X for Plan 9, together with linuxemu, it can be
used to run Linux software natively (see section 8.7.1 in the 9front fqa). This
implementation is not perfect however, it is old and tedious to work with, and
I have had little success with it myself.
Virtualizing other operating systems
[oses]
There are many different virtualization solutions available for UNIX/Windows
capable of running Plan 9, such as qemu and VirtualBox. Plan 9 has very limited
hardware support, especially if you want to use the classic versions of this
operating system. Virtualization is a practical way to eliminate such concerns.
9front also includes its own hypervisor (see section 8.7.5 in their fqa), vmx,
capable of running Linux, OpenBSD, allegedly Windows, and plausibly other
operating systems. PS: You need modern Intel hardware for this to work.
Basics
[first_miss]
I assume you have already downloaded and installed Plan 9, either on a physical
machine or on a virtual one. If not you can get the 9front iso, and follow the
installation instructions in section 4 of their fqa. Again, this is not a guide
for installing and configuring a Plan 9 system, use the 9front fqa for that.
Our focus here is doing day-to-day tasks after the initial setup is done.
PS: This is also the subject of section 8 in the 9front fqa - Using 9front.
This article simply repeats and expands upon some of that content.
PS: If you want to install 9legacy, it follows much the same steps as 9front,
but here are a couple of tips: After hitting Return at the "Location of
archives [browse]:" prompt, you will see /%, just type exit to continue the
installation. Choose plan9 when asked to "Enable boot method", otherwise just
follow the defaults and choose "y" at yes or no prompts. Finally: when
installing 9legacy in qemu, be sure to set the virtual harddisk as the first
disk drive, eg. qemu-system-x86 -m 2G -hda 9legacy.img, do not use -hdd or
similar, otherwise boot setup will fail during installation.
Window Management
The window manager in Plan 9 is called rio, it provides a remarkably clean and
simple desktop, somewhat akin to twm in UNIX. Unlike twm though, it doesn't
look like crap by default, and the source code is only 6000 lines of code,
which incidentally is also about the same size as Plan 9's graphical library,
libdraw. In contrast twm's source is closer to 30,000 lines, and the X Window
System backend, more then 8 million!
Window management is straight forward: rio provides only one menu, which you
can access by right clicking the mouse on the desktop background. Hold down the
mouse button while you are selecting a menu option, and release the mouse
button only after you have made your choice. To create a new window, which is
always a terminal, choose New. The mouse pointer changes to a cross. Right
click in a corner and drag the mouse, a red rectangular box appears, release
the mouse button when the window has the size you want.
If you choose the Delete option in the rio menu, the mouse pointer changes to a
cross with a circle. Right click on the window you wish to delete. If you Hide
a window, it will appear in the rio menu, select it from the menu to make it
visible again.
You can also Resize and Move a window by using the rio menu, but it's easier to
click and drag the windows directly: To resize a window, left click the blue
border and drag, to move it, right click and drag.
Right clicking in a terminal window will also bring up the rio menu, but other
programs will not necessarily do so. If you need to access the window manager
menu while running a fullscreen acme window for instance, you must first shrink
the window or move it out of the way, and then right click the gray rio
background. By default there are no key-bindings to control rio, you can only
do so using the mouse (What?!? Mouse actions are required?!? I know right, Plan
9 is so radical - take a look at the workspaces section below though).
Copy Pasting
In order to use Plan 9 effectively, you need a 3-button mouse. Such mice are
quite common nowadays, with the scroll wheel doubling as the middle mouse
button (for laptops I recommend ThinkPads). The 3 mouse buttons, and
combinations of clicks, are used throughout Plan 9 for manipulating text. If
you don't have a mouse with 3 buttons, you can simulate the middle click by
holding down the Shift key and right clicking. But this will quickly become
tedious, so go out and buy a 3-button mouse ASAP.
You can select text in the normal way, by left click and drag. You can also
double left click a word to select it. If you double click the end of a line,
the whole line will be selected, and if you double click a parenthesis, or
square bracket or some such delimiter, the text inside these parenthesis will
be selected.
To cut the selected text, hold down the left button and click the middle mouse
button. To paste the text, click the left button and while holding it down,
click the right button. To "copy" text, left click and middle click, release
the middle mouse button and click the right button. Such combinations of mouse
clicks are called mouse chording. They are used very consistently in Plan 9
programs, and feel intuitive enough once you get the hang of it.
Essential Programs
[apps]
There are only a handful of programs in Plan 9, they are simple to learn and
work very well. Some essential applications are:
• rio the window manager
• rc the shell
• acme a text editor, and more!
• mothra the web browser (use abaco in classic Plan 9)
• page a document/image viewer
• play, zuke music players (use juke in classic Plan 9)
• stats monitoring system load
Manipulating Text in the Terminal
You do not have to play around much in the Plan 9 terminal before you realize
that it works quite differently from UNIX. One surprise is that terminals do
not auto scroll, if you cat a very long file for instance, it will just display
the first screenful of text, and wait for you to manually hit PgDn or the down
arrow key. This behavior is actually very convenient, since it removes the need
for pagers. But sometimes it can cut against you. If you're compiling software
for instance, the compilation will stop once the text has filled the screen,
and only continue if you manually scroll down. Clearly, this is not what you
want! Middle click the terminal window and select scroll, it will now auto
scroll, just as UNIX terminals do. You can go back to the default behavior
again, by middle clicking and selecting noscroll.
Another annoyance might be that there is no Tab auto-completion. Don't worry,
use Ctrl-f instead, it does much the same thing. There is no advanced
auto-completion of program names and flags, like zsh and fish users might be
accustomed to. But this really isn't an issue since Plan 9 has virtually no
programs or flags to speak of, as you will discover soon enough.
The third thing you may notice is that the terminal text can be freely edited.
You can add any text anywhere and copy paste the text arbitrarily, the Plan 9
terminal thus feels much more like a text editor then a UNIX terminal (a
consequence of this free-form text editing is that the mouse cursor has to be
put at the end of the last line in order to execute a command with the Return
key, otherwise it will just add a literal newline to the text - this is only
mildly annoying once you get used to it). What's the point of this novel
design? First of all it eliminates a host of special purpose programs that UNIX
requires, for example there is no clear command in Plan 9, you just cut the
text. There is no reset or readline either, as they are not needed. Secondly,
once learned, this behavior feels very intuitive. Why shouldn't you be able to
cut and paste text and freely sprinkle your terminal output with random
comments? Going back to a UNIX terminal, after having spent some time in Plan
9, really feels like leaving the 90's - and going back to the 70's (fun tip:
check out /bin/hold to see how a basic text editor in Plan 9 can be written in
just five lines of shell script!).
Lastly, there is no history command in the Plan 9 terminal, hitting the up
arrow key on the keyboard will just move the pointer one line up, like any text
editor would. - What else did you expect? Relax though, you can rerun the
previous command with "" (" will reprint it).
Hang on! The command "", isn't double quotes used for quoting?!? Not in Plan 9,
double quotes are just ordinary characters. Whereas UNIX has three escape
characters, Plan 9 has only one, the single quote (well, OK, backslash is also
used in some situations). The UNIX command "$message has a literal \$ and '
sign", would be ''$message' has a literal $ and '' sign' in Plan 9 (two single
quotes within single quotes is interpreted as one literal single quote).
PS: " and "" are actually shell scripts, provided by 9front, classic Plan 9
systems do not have these.
Back to our topic of rerunning commands, note that the need to auto-complete
text and rerun commands are much greater in UNIX then in Plan 9. It is easy to
copy paste text in the terminal, so use that functionality for what it's worth!
You don't need to use insane syntax like ls !$ to run ls with the previous
arguments, or ^foo^bar to spell correct the last argument and rerun it. Just
type ls in the terminal and copy paste the previous arguments, and if you need
to spell correct the last argument, then just do so, copy paste the result when
you're done. There is also a full copy of the terminal text in /dev/text. So
the command cat /dev/text > transcript is essentially the same as script in
UNIX, > /dev/text is basically clear, and the command grep '^; ' /dev/text the
same as history (assuming of course that your shell prompt is ; ). Note that
you can search this log for other things then just your previous commands, and
you can manipulate this data in many other interesting ways as well. For
example, need to do advanced searching or manipulation of the shell history?
Just open /dev/text in a text editor, eg. sam /dev/text.
But what if you want a system wide history log for all of your windows? There
is no such file in Plan 9, but it's easy enough to make one. For example, the
following script will save your command history to a central file. Only unique
commands are saved, if we saved all of the text, our central history file would
grow extremely large. For example, it would be quite redundant to have ten
thousand entries of cd in our history log, not to mention hundreds of copies of
the manpages and text files we have been reading.
!/bin/rc
savehist - prune and save command history
usage: savehist
set some defaults
rfork ne
temp=/tmp/savehist-$pid
hist=$home/lib/text
touch $hist
rewrite history
cat <{grep '^; ' /dev/text} $hist | sort | uniq > $temp
mv $temp $hist
With this in place we can run savehist before exit to save our current history,
or we can wrap these steps into one by adding something like this to our $home/
lib/profile: fn quit{ savehist; exit } (PS: Don't call this function exit
unless you really want a fork bomb!)
In addition to /dev/text you also have /dev/snarf, which holds the "snarf"
buffer, the clipboard in Plan 9 speak (if you want to write to the window, use
/dev/cons). All of these files refer to your current window, if you want to use
these files for a different window, see the rio scripting section below.
The graphical desktop runs "within" the text console in Plan 9, so writing to
the system console will actually print the text verbatim onto the screen. For
example, running sleep 600; echo Bug Me! > '#c/cons' will send a fairly
obtrusive notification to your screen in 10 minutes. This can be a bit
disconcerting for a beginner, but it's easy to redirect such messages if you
don't want them to clutter up your screen. Just run cat /dev/kprint in a window
and hide it. See the rio scripting section below, for some ideas on how to
avoid or abuse this functionality further.
Acme - The Do It All Application
[acme]
The acme text editor is arguably the main user application for Plan 9, it
doubles as the systems file manager, terminal, mail reader and more. It can
even be used as a fully fledged window manager, by replacing rio with acme in
your $home/lib/profile (but I don't recommend it - you will not be able to run
any other graphical programs - then again, why would you want to?).
Let's do a whirlwind tour of acme: The first blue row contains commands for the
entire acme window, such as Exit, if you middle-click this button, acme will
exit. Dump will create a file called acme.dump, this can be used to save a
particular window arrangement, and restored with acme -l acme.dump. Putall will
save all modified text files.
If you middle-click Newcol a new column will appear. The column has it's own
row of commands, in the second blue row. Delcol will delete the column. Cut,
Paste and Snarf (eg. "Copy"), will do text manipulations. But it's easier to
use mouse chords for this: Left and middle-click to Cut, Left and right-click
to Paste, and finally Left and middle-click, then right-click to Snarf, or
Copy. The mouse chords are awkward to explain, but try it out, it will feel
very intuitive with a little practice.
Middle-clicking New will create a new window in the column. Again, it too, will
have it's own row of commands. Del will delete the window. The window is
initially empty, try writing some random text into it. You will see that a new
command appears, Undo (it's meaning should be obvious). After typing in some
text, you can also hit the Esc key to mark the recently added text, hitting Esc
again, will cut the text. How do we save our file? First we need to give it a
name: Click on the far left side of the menu, left of Del, and type /usr/glenda
/testfile (glenda is the default user in Plan 9, and /usr/glenda is the default
home directory). Yet another command will appear, Put, middle click it to save
your work. That was a lot of typing! Isn't there an easier way to do this?
Sure, remember that Plan 9 allows you to copy paste pretty much anything. Find
the directory you want in a terminal, with Ctrl-f auto-completion and
everything, then print the directory name with pwd, and just copy paste that
into the acme window, and append your new filename. Easier yet, run touch
testfile; B testfile from a terminal and the file will be opened for you in
acme.
By now you will have noticed a very unique feature of acme, it's menus are pure
text. The "buttons" are just regular words. To illustrate: Type Del (case
sensitive!) somewhere in the yellow text window, then middle click it. The
window will disappear. Del is just a command, same as echo or cat. Another
test: Type echo hi there and middle click, and drag, so that the red mark
covers all three words. hi there will be printed in a new window.
You can use the Look command to search for words in the window. Type monkey a
couple of times in the yellow text window, now type Look monkey in the blue
window menu, and middle click and drag, to mark the two words. The first
occurrence of monkey will be highlighted, run the command again, and the second
occurrence will be highlighted, and so on. An easier method however would be to
just right-click the word monkey, anywhere in the text, the next occurrence of
the word will be highlighted, and the mouse pointer will be moved there. Just
right-click again to see the next occurrence of the word, and so on.
The Zerox command in the column menu will duplicate a window, this is very
useful if you are editing a long file, and you need to see or edit different
parts of the file at the same time, any changes made in one window will appear
in the other. Sort will sort the column windows by name, it does not sort the
content of the windows. To do that, mark the text, type |sort in the window
menu, and middle-click it. As you can see, you can freely use arbitrary Plan 9
commands to manipulate the text in acme.
If you want to do search and replace operations, use the Edit command. This
command is a back end for the sam text editor, which uses much the same text
editing commands as ed (which again is similar to sed or vi). For example,
double click one of the monkey words to highlight it, then type Edit s/monkey/
chimpanzee/, and middle click and drag to execute this command. The highlighted
word will be changed to chimpanzee. To change all the occurrences of monkey,
type Edit ,s/monkey/chimpanzee/g (in vi this would be :%s/monkey/chimpanzee/g).
Side note: Although the above ed style substitution works in sam, sam is not a
line-based editor like ed, and a more proper sam command for the above would
be: Edit ,x/monkey/c/chimpanzee/ (that is: for each /monkey/ change to /
chimpanzee/). To read the sam tutorial, run: page /sys/doc/sam/samtut.pdf
acme lacks many built-in features that a UNIX user might expect, but you can
create much of this functionality simply by piping the text through standard
utilities. Here are some examples:
• Edit = print current line number
• Edit ,|sort -r reverse sort the file
• Edit ,|sed 's/^/ /g'|grep -n . add line numbers
• Edit ,s/^[^:]+: //g remove line numbers
• Edit s/^/ /g indent text
• Edit s/^ //g unindent text
• Edit s/^/#/g comment out lines of code
• Edit s/^#//g uncomment lines of code
• Edit ,|wc -c file word count
• Edit ,|fmt nicely format the file
• Edit ,|cb beautify C source code
• Edit s/./-/g underline after copying a line
• |tr A-Z a-z lowercase text
• |tr a-z A-Z uppercase text
• |tr a-zA-Z n-za-mN-ZA-M rot13 text
Open a New window and type in the filename /usr/glenda to the far left, then
type Get to the far right, right of Look, and middle click it. The contents of
the /usr/glenda directory will fill the window. If you right-click on a
directory in this window, the contents of that directory will be opened in a
new window. To do operations on files, just type a command and execute; for
example type rm before testfile, and middle click the two words to remove this
file. If you right-click a text file, the contents of that file will be opened
for editing in acme.
Exactly what happens when you right-click something in acme, depends on the
word you click. For example clicking on the word /usr/glenda/pictures/cirno.png
, will open this picture in the image viewer page, and clicking jazz.mp3, will
start playing the audio file with play. Provided of course that the files in
question exist on your system. The last example also assumes that the jazz.mp3
file is located in the same directory as the one you launched acme from, if not
you need to specify a correct file path. The actual work of connecting the
right words to the right programs is handled by plumber, which we will talk
about later, but for now it's enough to know that right clicking a filename
anywhere in acme will usually just "do the right thing" (you'll note though
that actions are evaluated for words, not files).
Each window has a dark blue square to the far left of the menu, you can click
and drag this box to resize or move the window to another column. The columns
themselves also have a dark blue square, click and drag this to resize or move
the column.
You can also right-click on the dark blue window square, to hide all the column
windows except that one, left-click on it to bring the windows back.
Left-clicking on the square will increase the window size a little,
middle-clicking will maximize the window.
Left-clicking on the scroll bar will scroll upwards, right-clicking downwards.
Clicking towards the bottom of the scroll bar will scroll a lot, clicking
towards the top will only scroll a little. Middle clicking will transport you
directly to that portion of the file. Play around and experiment with these
mouse actions, pretty soon you will get the hang of it. Other Plan 9
applications with scroll bars work the same way (in 9front at least).
Multiple Workspaces
[rio]
rio does not have virtual workspaces per se, but 9front includes some tools
that let you set up a keyboard driven desktop with pseudo-workspaces. For
instance, you can add the following snippet to your $home/lib/profile:
fn workspaces{
</dev/kbdtap riow >/dev/kbdtap |[3] bar
}
You can now run workspaces and switch to a new "workspace" with Win+n (where
Win is the Windows key, between the left Ctrl and Alt keys, and n is a number
between 0 and 9). You can also move windows about with Win+Arrow, or resize
them with Win+Shift+Arrow (see riow(1) and bar(1) for more info). Classic Plan
9 does not have these tools, although there is an old fork of rio called
rio-virtual, that does include virtual workspaces. There are also ways to
create such services without the 9front extensions: You'll note that all
windows in all riow "workspaces" are listed in the rio menu and can be
unhidden. This is because riow doesn't actually add workspaces as such, but
rather provides a clever way of hiding and unhiding groups of windows, and thus
gives you the illusion of workspaces. For a similar, but more simplistic, way
to do this see the rio scripting section below.
It is actually quite easy though to manually create pseudo-workspaces in rio:
Just create a new terminal window and run plumber ; rio. This will run a rio
desktop in this window (plumber is not required here, but it will make sure
that files automatically opened will only be opened in this isolated rio and
not outside of it). You can maximize this "virtual workspace" and do your work,
hide this window when you want to go back to your first workspace, then switch
back to it by selecting the hidden window in the rio menu. You can have as many
of these workspaces as you like, and you can run rio inside rio inside rio ad
infinitum... To organize this mess a bit you can also manually label your
workspaces. Lets say you have 4 rio workspaces hidden in the background, the
rio menu will just list them as: rio, rio, rio, rio. That's not very helpful.
By running grep rio /mnt/wsys/wsys/*/label you will see what window id these
workspaces have. You can then redefine their label, eg. echo -n workspace1 > /
mnt/wsys/wsys/3/label. The rio menu will now list this window as "workspace1",
instead of "rio".
Another simple workspace solution is drawterm. Once a Plan 9 CPU server (see
section 7.5 and 7.6 in the 9front fqa, and the Quick CPU+AUTH+Qemu+Drawterm
HOWTO section below) has been configured, you can connect as many drawterm
clients to it as you wish (all of the workspace related tricks mentioned above
will also work from within drawterm).
Tiling Windows
[tiling]
First of all, acme is a tiling window manager. Just maximize the editor and do
your stuff.
Secondly, you can use your rio startup file, $home/bin/rc/riostart, to
automatically set up a desktop that suits your needs. For example, if you have
a 1366x768 screen, you can add these instructions to add an acme window to the
left half of the screen, and a terminal window on the right half:
window 0,0,683,768 acme
window 683,0,1366,768
Unlike UNIX, graphical programs executed in a Plan 9 terminal will not launch a
new window, rather, the terminal will morph into this new program. In other
words, running the PDF/Image viewer page, or the web browser mothra in a
terminal for instance, will in no way effect window placement. So having an
initial window placement that works on your desktop, will significantly reduce
the need for automatic window tiling. But if you need that, take a look at the
rio scripting section below.
Plumbing
We have already seen brief mentions of the Plan 9 plumber a few times in this
guide, but lets take a closer look. The plumber is essentially a simple
inter-process messaging system. It lets you define a set of actions based on
text patterns given to it. For instance, in the system wide plumber rules in /
sys/lib/plumb/basic, you will find the following section:
open urls with web browser
type is text
data matches 'https?://[^ ]+'
plumb to web
plumb client window $browser
This rule is very simple: If the message is text (it's always text), if it
matches "http://" or "https://" something or other, define it as "web" related,
and launch a new program, "$browser", with the given text as arguments. So in
effect, whenever an URL is sent to the plumber, it opens it up in your default
web browser. So, right clicking http://9front.org in acme will open up that web
page, likely in mothra. You can also run the command plumb http://9front.org in
a terminal, for the same effect.
You can define your own rules too. A common thing to do is to edit the editor
and browser variables to modify the default text editor and web browser. I also
wrote my own simple Epub reader, and added these lines to $home/lib/plumbing,
in order to always open Epub files with my custom reader:
change default apps
editor = acme
browser = 'mothra -a'
open epubs with custom script
type is text
data matches '([a-zA-Z0-9_\-./]+).(epub|EPUB)'
arg isfile $0
plumb to image
plumb start window eread $file
load default rules
include basic
The epub rule here adds a check to see if the given argument is an existing
file, if it is $file is set to this filename, but the logic is otherwise much
the same as the above URL rule. The final include basic line at the end is
important, without it you would loose all the default system plumbing rules!
Plumbing rules are not restricted to file suffixes. Suppose you are reading
through several documents at the moment, and you want to bookmark these to keep
track of your reading progress. The solution is simple, write a database, lets
call it $home/lib/bookmarks, with content similar to this:
work stuff
/usr/glenda/doc/papers/lengthy.pdf!123
/usr/glenda/doc/papers/plain.txt:206
plan 9 stuff
/sys/doc/9.ps!3
/sys/doc/acme/acme.ps
acme(1)
fun stuff
/usr/glenda/doc/books/peter_pan.txt:/Chapter 2/
/usr/glenda/music/podcasts/bsdnow/acdecc6a-f7b7-4d64-b64d-f7be713b78e2.mp3
Right clicking on any of these lines in acme, will open up the file with an
appropriate program. page for PDF's and postscript files, play for audio files,
and plain text files directly in acme. But the default plumbing rules allow you
to be even more specific then that. Piping something like lengthy.pdf!123, will
not only open the PDF in page, but also on page 123. Plain text files can also
be addressed, such as plain.txt:206 for line 206 of that file, or
peter_pan.txt:/Chapter 2/ to open up our Peter Pan book and look for the text
string "Chapter 2". Usually such textual plumbing rules are used when
programming, to open a source file on the offending line by right clicking a
diagnostic message for instance, but we can also used them to keep track of
ourselves.
Speaking of which, lets look at one more example of how we can modify plumbing
rules to suit our workflow. in the PIM section below, I mention a script called
que, which iterates over a list (a queue), by printing the next line in the
file whenever you run que on it. Lets assume we have a list called $home/lib/
que/peterpan with the following content:
/usr/glenda/doc/books/peter_pan.txt:/Chapter 1/
/usr/glenda/doc/books/peter_pan.txt:/Chapter 2/
/usr/glenda/doc/books/peter_pan.txt:/Chapter 3/
...
Now, each time we run que $home/lib/que/peterpan, it will tell us what chapter
to read next in our book. And sure enough, we can right click this output in
acme to open up the book in the right place (since "Chapter x" contains
whitespace we need to right click and drag to mark the whole line). But that is
waaay too much work for a lazy pants such as myself! What I really want is just
to add something like this to my bookmark database:
/usr/glenda/lib/que/peterpan:que
Right click this in acme, and have it automatically call que and open up the
right chapter for me. As it turns out, such automation is easy-peasy, I just
need to add this plumbing rule to my $home/lib/plumbing (and update my rule set
with the command: cp $home/lib/plumbing /mnt/plumb/rules):
plumb the next item in a queue file
type is text
data matches '([a-zA-Z0-9_-./]+)(:que)'
arg isfile $1
plumb to none
plumb start rc -c 'plumb `{que '$file'}'
This rule checks if the plumber received "something_something:que", and that
the first argument (excluding the :que) was a real file. We are not interested
in opening this file, so we plumb it to "none", and then we run our shell
command plumb `{que $file}. Of course our queue doesn't need to be plain text
chapters, they could be PDF's with page numbers or sequential audio files in a
podcast, or what have you.
We can abuse the plumber in all kinds of fun and potentially destructive ways.
It basically allows you to define any text pattern and connect that to any
command. Even if you don't go bananas with this, it is an eye opening
experience to read /sys/lib/plumb/basic and realize just how simple
inter-process messaging can be!
System Administration
[red_button]
Basic System Administration
To shutdown or reboot a Plan 9 system, you can use the fshalt and reboot
commands. If you are using a remote Plan 9 desktop, such as drawterm, it is
safe to just kill the application directly. The remote desktop is stateless,
and thus shutting it down will in no way effect the host file system. In fact,
the system is designed to run a CPU server 24/7, connected to diskless clients
where the users do their actual work. Probably because of this design, Plan 9
file systems do not traditionally try to recover from a power loss, so don't
pull the plug on your file server!
For cpu servers, using tried and true file systems is not a bad idea, but for
laptops with a high risk of power loss, it might be wise to use the new GEFS (A
Good Enough File System) filesystem from 9front, which does a decent crash
recovery, with plans of self-healing and other nifty stuff in the offing. (of
course, this is still no a substitute for backups):
PS: fshalt does not work right in qemu if you use classic Plan 9, such as
9legacy. In such cases you should write your own shutdown script, like so
(note: this is not an issue in 9front):
!/bin/rc
halt - shutdown file server
usage: halt
echo fsys main sync >>/srv/fscons
sleep 5
echo Its now safe to turn off your computer
echo fsys main halt >>/srv/fscons
To monitor your remaining battery, memory usage, ethernet traffic, system load
and other resources, you can use the stats and memory commands. Simply cat'ing
around in /dev will also provide much system information, for instance cat /dev
/kmesg is essentially equivalent to dmesg in UNIX. There is also limited
support for suspend and hibernate if you add the apm0= value to plan9.ini (see
section 9.2.3 in the fqa and apm(8)). Don't expect this to work though, ACPI
and APM is a hairy business!
PS: memory is just a simple shell script in 9front that cat's /dev/swap and
reformats the values in more human readable form, classic Plan 9 systems do not
have this script.
Battery Monitoring
Speaking of not working, battery monitoring usually doesn't in my experience
(to check if it works on your box, just run stats, right click and add
battery). And unless you are very lucky, plugging in a headset will not
automatically redirect audio output either. I had both problems on my cheap
Acer laptop (note to self: only buy ThinkPads from now on). The last issue will
be revisited in the audio section below, as for battery monitoring, a very
simple workaround is to run sleep 7200; echo Warning: batteries about to go
out! > '#c/cons'. Assuming that your computer has 2 hours (7200 seconds) and 15
minutes of battery capacity, and you run this command when you know that the
machine is fully charged, you will get notified 15 minutes before your battery
runs out.
The main problem with this elegant solution, is that it does not work at all if
you expect to reboot your computer at some unknown point in the future. I find
that this is frequently the case when I am traveling, and need battery
monitoring the most. So I need a way to start a 2 hour countdown that persists
across reboots, this script does the trick:
!/bin/rc
batt - print estimated remaining battery power
usage: [battery=min] batt [-]
#
bug: the script doesn't actually know anything about your battery,
the user is required to run batt - initially to set a timer.
set some defaults
rfork e
if(~ $battery "") battery=120 # hardware dependent
capa=$battery
batt=$home/lib/battery
stat='Battery at %p%% estimated remaining time: %r min'
mesg='Your battery is about to run out!'
ping=$home/media/music/samples/mario.mp3
parse arguments
switch($#*){
case 0
if(! test -f $batt){
echo 'batt: countdown hasn''t started, run batt - first!' >[1=2]
exit notstarted
}
used = `{cat $batt}
pros = `{echo 100 - ($used^00 / $capa) | hoc | sed 's/\..*//'}
remn = `{echo $capa - $used | hoc}
echo $stat | sed -e 's/%p/'$pros'/' -e 's/%r/'$remn'/' -e 's/%%/%/'
case 1
# -, start or continue a countdown
if(! test -f $batt) echo 0 > $batt
while (sleep 60) {
date > $home/lib/end
used = `{echo `{cat $batt} + 1 | hoc}
if (test $used -ge $capa) {
echo $mesg >'#c/cons'
if(test -f $ping) play $ping >[2]/dev/null
rm -f $batt
exit
}
echo $used > $batt
}&
case *
echo 'Usage: [battery=min] batt [-]' >[1=2]
exit usage
}
You'll note that this simple countdown script measures time in minutes (120,
not 7200), the main reason for this crude measurement of time is battery
related, if we counted every second, the script would be 60 times harder on our
battery. Anyway, using this script you can start a countdown when you know the
battery is fully charged with the command batt - (or battery=80 batt - or
whatever to set a countdown other then the default 120 minutes). Once that
daemon has started, run batt to get an estimated remaining time of juice. But
here comes the clever part: After a reboot run batt - to continue a battery
countdown! In fact, you can fully automate this step by adding something like
this to our $home/lib/profile:
battery=80 # default battery capacity
if (test -d /mnt/term/dev){
# do drawterm stuff
...
}
if not {
# do non-drawterm stuff
if(test -f $home/lib/battery) batt -
...
}
Don't let the boilerplate here scare you. If you don't use drawterm, just add
if(test -f $home/lib/battery) batt -, and you're done (but you probably don't
want to mess with battery stuff if you are using drawterm, for obvious
reasons). This command simply checks to see if the file that the batt daemon
uses to measure the battery countdown exists. Since our batt script removes
this file once the countdown has expired, it knows that an unfinished countdown
was in progress before the last reboot, and so it respawns the daemon. This is
also a convenient place to set your default battery capacity. Of course, you
could just edit the batt script, but if you are using this on multiple laptops,
setting such a value in $home/lib/profile might be more practical.
Finally, to know when the laptop is done recharging from a depleted battery,
just measure the time it takes in Ubuntu, or other suitable grandma distro, and
set an appropriate timer in Plan 9. We could also wrap this up in a simple
script that interrupts a battery countdown and cleans up the temp file:
!/bin/rc
recharge - estimate when battery is recharged
usage: recharge
slay batt | rc
rm -f $home/lib/battery
sleep 1800
echo 'Battery is fully charged!' > '#c/cons'
Our script is quite unintelligent, but in my opinion it is a nice example of
how you can create fairly useful and simple workarounds on UNIX-like operating
systems, even when they lack vital features. (it's probably a good idea to use
the crash resistant GEFS filesystem on laptops too - in case our makeshift
battery script doesn't provide enough warning)
Configuring Startup and Shutdown
Plan 9 has no /etc directory like UNIX, instead it is configured through a
small handful of files. The most important of which is probably $home/lib/
profile, the user startup file. This is where you customize your user specific
settings, it is somewhat analogous to ~/.profile in UNIX, but more important
since desktop and shell are much more integrated in Plan 9. Personally I like
to add this line to my lib/profile: . $home/lib/aliases, which enables me to
add custom aliases to this separate file, while keeping only system related
configurations in lib/profile. But that is just a matter of taste.
Beware that the settings in $home/lib/profile needs to cater to very different
situations! Whether you are booting a CPU server, a standalone "terminal", or a
diskless one, or are logging in through a remote connection (rcpu or drawterm
for example), they all read lib/profile, but often need different
customization's. The moral is, be careful when editing your profile, hubris
cause debris.
The kernel configuration is in the plan9.ini file, which resides in a special
boot partition. To read the contents of this partition you must first run 9fs
9fat (for classic Plan 9 run 9fat:), you can then read this file in /n/9fat/
plan9.ini (note: like all Plan 9 commands this manipulates the namespace of
your current process, so you will not see this file in other processes). It is
by editing this file that you configure your system to run as a CPU server or
terminal, you may also need to tweak some hardware specific values here. See
plan9.ini(8) and section 3 of the fqa.
Network configuration is handled in /lib/ndb/local, with additional related
files in that directory. But you don't need to mess around with this file if
you just want to quickly connect to the internet on a laptop (see section 6 in
the fqa). Mail configuration is handled by a number of files in /mail/lib (see
section 7.7 in the fqa).
Lastly there is also a desktop specific startup file in $home/bin/rc/riostart,
which is useful for specifying what programs and windows to auto launch, it is
discussed in the tiling windows section of this article.
Wallpapers, themes, etc...
[cant_watch]
The rio window manager is painstakingly crafted with love and care to look as
boring as humanly possible. This is important - a distraction free environment
is a productive environment. But it is possible to install 3rd party patches
that let you customize the rio theme and set a wallpaper, if you really crave
such frippery (this will not work for classic Plan 9 however):
[riotheme]
Installing theme and wallpaper patch for 9front rio:
install rio-themes
; bind -ac /dist/plan9front /
; cd /sys/src/cmd/rio
; hget https://ftrv.se/_/9/patches/rio-themes.patch | patch -p5
; mk install
; reboot # or otherwise restart rio
write a theme, eg. in $home/lib/theme/rio.theme
ps: wallpaper must be in the plan 9 image format,
eg. jpg -9t <pic_1920x1080.jpg >$home/lib/1920x1080.img
rioback /usr/glenda/lib/1920x1080.img
back f1f1f1
high cccccc
border 999999
text 000000
htext 000000
title 000000
ltitle bcbcbc
hold 000099
lhold 005dbb
palehold 4993dd
paletext 6f6f6f
size 000000
menubar 448844
menuback eaffea
menuhigh 448844
menubord 88cc88
menutext 000000
menuhtext eaffea
use your theme (add it to riostart if you want)
; window 'cat $home/lib/theme/rio.theme > /mnt/wsys/theme;
sleep 0.5;
grep softscreen /dev/vgactl >> /dev/vgactl;
echo hwblank off >> /dev/vgactl'
PS: Although the rio window manager works great, it is getting a little long in
the tooth. There have been several attempts at extending or rewriting it over
the years. The most promising variant in my opinion is lola. Please do test it
if you're curious, it's a new project, and the developer would appreciate your
feedback.
Internationalization
[english]
For better or worse, computing is an English affair. I'm sorry, but if you want
to program and use any operating system in any professional capacity, you need
to learn the English language. Nearly all vital documentation, and any defining
works in programming, computer science and computing history, will be written
in this language. I don't mean to be unsympathetic here, I am not a native
English speaker myself, so I know that this can be a tall order, but that's
just the way it is.
Having that said, technically speaking, Plan 9 does have very good
internationalization support. Of course, all of the instructions given during
installation, and all of the available documentation is in English. But the
system itself supports most languages as everything is Unicode throughout.* So
as long as you have the necessary fonts installed, you can read and write any
language (well, languages that aren't written from left to right will require
some work). UTF-8 was in fact invented by the Plan 9 developers! For example,
to write the Northern Norwegian sentence "Æ e i Å æ å" (yes, this is a real
sentence* ), type Alt+Shift+a+e e i Alt+o+Shift+a Alt+a+e Alt+o+a. A list of
the international characters available with the Alt key combo, can be found in
/lib/keyboard. So to find out how to write a smiley face in Plan 9, just type
grep ☺ /lib/keyboard (naturally the ☺ can be copy pasted), and it will print:
263A :) ☺ smiley face
That is, type Alt+:+) to produce the Unicode character 0x263A, aka a smiley
face. You can change the default US qwerty layout with the kbmap command, right
click on the layout you want, then type q to quit. To set this change
permanently:
change dvorak to whatever layout you prefer
setting layout in 9front:
; 9fs 9fat
; echo 'kbmap=dvorak' >> /n/9fat/plan9.ini
setting layout in classic Plan 9:
; sam $home/lib/profile
add the following line somewhere near the top
; cp /sys/lib/kbmap/dvorak /dev/kbmap
Modifying an existing keyboard layout is fairly easy, just copy the layout in /
sys/lib/kbmap and open it in a text editor. The fields are "layer" (normally 0
for direct key press, 1 for Shift+Key or 4 for Ctrl+Key), keycode, character
(eg. 'M for the letter M, ^M for Carriage Return). Writing a layout from
scratch is more work, but doable. (see kbdfs(8) for more info)
Working with Non-Latin alphabets
As touched upon in the previous section, you can choose a Non-Latin keyboard
layout, such as ru (Russian) or fa (Farsi, ei. Persian). Sometimes though, you
may need to work with a language other then your input language of choice. For
example, I use English as my day-to-day system language, but every so often I
need to work with some Russian or Persian text. (my wife is Russian/Ukrainian
and I've dabbled a bit with Persian as a hobby) I guess I could learn to
touchtype the Russian keyboard layout, but for now it's too much of a hassle. I
could type in Russian words using Alt key combos, eg. Alt+@P Alt+@r Alt+@i
Alt+@v Alt+@e Alt+@t would spell Привет.* But that is a lot of typing! We could
make a more convenient tool with sed:
!/bin/rc
cyr2utf - convert ASCII Cyrillic representations to UTF-8
usage: cyr2utf < in > out
bugs: uses arbitrary non-standard conventions
use lat2utf if you need to *mix* Latin and Cyrillic!
#
guide:
Most of the Cyrillic letters are mapped to their vocalization
equivalents in the Latin alphabet, eg: A, B, V, G, D, E, Z, K, L, M,
N, P, R, C/S, T, and F map perfectly to А, Б, В, Г, Д, Е, З, К, Л, М,
Н, П, Р, С, Т, and Ф. I, J, O, U, H and Y are fairly close to И, Й,
О, У , Х and Ы. A few letters are mapped to Cyrillic letters that
look like the Latin letters, such as X, Q and W correspond to Ж, Ц
and Ш.
#
Finally, some Cyrillic letters must be written by an escape '\'
character followed by one or two Latin letters, such as \E, \:E/\JO,
\Q, \W, \JU, \JA, \h/\hard and \s/\soft corresponding to: Э, Ё, Ч, Щ,
Ю, Я, ъ and ь.
sed -e 's/\\E/Э/g' \
-e 's/\\e/э/g' \
-e 's/\\:E/Ё/g' \
-e 's/\\:e/ё/g' \
-e 's/\\JO/Ё/g' \
-e 's/\\Jo/Ё/g' \
-e 's/\\jo/ё/g' \
-e 's/\\Q/Ч/g' \
-e 's/\\q/ч/g' \
-e 's/\\W/Щ/g' \
-e 's/\\w/щ/g' \
-e 's/\\JU/Ю/g' \
-e 's/\\Ju/Ю/g' \
-e 's/\\ju/ю/g' \
-e 's/\\JA/Я/g' \
-e 's/\\Ja/Я/g' \
-e 's/\\ja/я/g' \
-e 's/\\hard/ъ/g' \
-e 's/\\h/ъ/g' \
-e 's/\\soft/ь/g' \
-e 's/\\s/ь/g' \
-e 's/A/А/g' \
-e 's/a/а/g' \
-e 's/B/Б/g' \
-e 's/b/б/g' \
-e 's/V/В/g' \
-e 's/v/в/g' \
-e 's/G/Г/g' \
-e 's/g/г/g' \
-e 's/D/Д/g' \
-e 's/d/д/g' \
-e 's/E/Е/g' \
-e 's/e/е/g' \
-e 's/Z/З/g' \
-e 's/z/з/g' \
-e 's/K/К/g' \
-e 's/k/к/g' \
-e 's/L/Л/g' \
-e 's/l/л/g' \
-e 's/M/М/g' \
-e 's/m/м/g' \
-e 's/N/Н/g' \
-e 's/n/н/g' \
-e 's/P/П/g' \
-e 's/p/п/g' \
-e 's/R/Р/g' \
-e 's/r/р/g' \
-e 's/S/С/g' \
-e 's/s/с/g' \
-e 's/C/С/g' \
-e 's/c/с/g' \
-e 's/T/Т/g' \
-e 's/t/т/g' \
-e 's/F/Ф/g' \
-e 's/f/ф/g' \
-e 's/I/И/g' \
-e 's/i/и/g' \
-e 's/J/Й/g' \
-e 's/j/й/g' \
-e 's/O/О/g' \
-e 's/o/о/g' \
-e 's/U/У/g' \
-e 's/u/у/g' \
-e 's/H/Х/g' \
-e 's/h/х/g' \
-e 's/Y/Ы/g' \
-e 's/y/ы/g' \
-e 's/X/Ж/g' \
-e 's/x/ж/g' \
-e 's/Q/Ц/g' \
-e 's/q/ц/g' \
-e 's/W/Ш/g' \
-e 's/w/ш/g'
With such a tool in place we can write a Russian greeting with: echo Privet |
cyr2utf. Of course languages are complicated. A person who only knows a single
language may naively believe that two dictionaries, one in each language, and a
word-for-word look up is all that is needed for translation. That is not the
case, two languages never overlap perfectly. Neither do alphabets. So our
cyr2utf tool is not a magic bullet, you need to have a passing familiarity with
the Cyrillic alphabet, and learn some idiosyncrasies of the script to boot. But
for someone who needs to type Russian text occasionally, the script can be a
timesaver.
For Persian, the above comments are also true, but more so. In addition to
being a weird alphabet (from a Westerners perspective), it is written from
right to left. Plan 9 does not have fribidi, but we can write a poor mans
equivalent in awk. With that in place we can also write a sed translator:
!/bin/rc
bidi - print bidirectional text
usage: bidi [files...]
bugs: tabs are statically changed to four spaces
text alignment requires a fixed width font
set some defaults
rfork e
if (~ $#* 0) files=/fd/0
if not files=($*)
print bidirectional text
sed 's/ / /g' $files | awk '
BEGIN { FS = "" }
{ row[NR] = $0
if (length() > max) max = length()
} END {
for(r=1;r<=NR;r++){
split(row[r], l)
line = ""
for(i=length(l);i>0;i--) line = line l[i]
printf("%" max "s\n", line)
}
}'
!/bin/rc
far2utf - convert ASCII Persian representations to UTF-8
usage: far2utf < in > out
bugs: basic implementation, non-standard conventions
annoyingly, an initial alef must be typed \i, not i
depend: fribidi
#
guide:
Most of the Persian letters are mapped to their vocalization
equivalents in the Latin alphabet, eg: b, p, t, s, j, c, x, d, r, z,
f, q, k, g, l, m, n, v/w, y map more or less to ب, پ, ت, س, ج, چ, خ,
د, ر, ز, ف, ق, ک, گ, ل, م, ن, و and ی. While some letters must be
escaped, such as \h, \j, \sh, \se/\th, \sd, \ta, \zl, \zd, \za, \e
and \q for ح, ژ, ش, ث, ص, ط, ذ, ض, ظ, ع and غ.
#
The alef ا, hamza ء, tasdid ّ and sukun ْ are written as i, \g, \w
and \o, since they look similar to these Latin letters. An initial
alef (آ) is written \i. The three vowel indicators are a, e, o for
َ, ِ, ُ. Letters with a hamza should be written \xg, eg. \ig for أ.
Standalone madda, "flying" hamza and under hamza (\m, \fg, \ug) do
not have a practical use. The tavine nasb (ً), jarr and raf signs
are \tn/\n, \tj and \tr. Kasida is \-. The rare characters ة, إ and
ڤ are written \h:, \iu (or i\ug) and \b.
#
Note that و must be written as v or w, since o is used for ُ, and
that ی must be written y, not i (ا) or j (ج). Unfortunately,
"alternative" s/z/t letters must be written as three digits (\xx).
sed -e 's/\\ig/أ/g' \
-e 's/\\vg/ؤ/g' \
-e 's/\\wg/ؤ/g' \
-e 's/\\yg/ئ/g' \
-e 's/\\hg/ۀ/g' \
-e 's/\\h:/ة/g' \
-e 's/\\fg/ٔ/g' \
-e 's/\\ug/ٕ/g' \
-e 's/\\iu/إ/g' \
-e 's/\\g/ء/g' \
-e 's/\\i/آ/g' \
-e 's/\\m/ۤ/g' \
-e 's/\\n/ً/g' \
-e 's/\\tn/ً/g' \
-e 's/\\tj/ٍ/g' \
-e 's/\\tr/ٌ/g' \
-e 's/\\w/ّ/g' \
-e 's/\\o/ْ/g' \
-e 's/\\-/ـ/g' \
-e 's/\\b/ڤ/g' \
-e 's/\\h/ح/g' \
-e 's/\\j/ژ/g' \
-e 's/\\e/ع/g' \
-e 's/\\q/غ/g' \
-e 's/\\se/ث/g' \
-e 's/\\th/ث/g' \
-e 's/\\sh/ش/g' \
-e 's/\\sd/ص/g' \
-e 's/\\ta/ط/g' \
-e 's/\\zl/ذ/g' \
-e 's/\\zd/ض/g' \
-e 's/\\za/ظ/g' \
-e 's/i/ا/g' \
-e 's/a/َ/g' \
-e 's/e/ِ/g' \
-e 's/o/ُ/g' \
-e 's/b/ب/g' \
-e 's/p/پ/g' \
-e 's/t/ت/g' \
-e 's/s/س/g' \
-e 's/j/ج/g' \
-e 's/c/چ/g' \
-e 's/x/خ/g' \
-e 's/d/د/g' \
-e 's/r/ر/g' \
-e 's/z/ز/g' \
-e 's/f/ف/g' \
-e 's/q/ق/g' \
-e 's/k/ک/g' \
-e 's/g/گ/g' \
-e 's/l/ل/g' \
-e 's/m/م/g' \
-e 's/n/ن/g' \
-e 's/v/و/g' \
-e 's/w/و/g' \
-e 's/h/ه/g' \
-e 's/y/ی/g' \
-e 's/1/۱/g' \
-e 's/2/۲/g' \
-e 's/3/۳/g' \
-e 's/4/۴/g' \
-e 's/5/۵/g' \
-e 's/6/۶/g' \
-e 's/7/۷/g' \
-e 's/8/۸/g' \
-e 's/9/۹/g' \
-e 's/0/۰/g'
We can now type echo slim | far2utf | bidi, and it will produce مالس.*
Unfortunately our simple bidi code does not work perfectly, it does not reshape
the Persian letters so that they blend together. (it would be like reading "H E
L L O" instead of "Hello", it's intelligible but ugly) To do so we would have
to write a more sophisticated bidi implementation in C. Of course, you could
also convert it with fribidi if you have a UNIX box handy. Once converted
correctly, Plan 9 will display the text just fine.
[internatio]
Now, playing with Non-Latin alphabets in the terminal is one thing, but if we
plan to work seriously with this, we need to add such text to a document. For
Russian, that's easy peasy, just write it directly in troff. (more on that in
the writing documents section below) For Persian though it's more tricky. Plan
9's version of troff does not support left-to-right input. (neither does it
include Persian fonts) The easiest way to do this is again to have a UNIX box
at the ready. You can install neatroff and read the short getting started
document. Of course, you can still write the ms files in Plan 9 and display the
PDF's that neatroff generates.
Now I'm sure Russian, Persian, not to mention Norwegian, documents are totally
irrelevant for you, but that's not the point. These are merely examples. The
point is that you can work with Non-English languages in Plan 9. And more
broadly, even when Plan 9 does have limitations, you can usually work around
them with an alternative OS at the ready.
User Management and Security
[faulty_cre]
The method of adding a new user to Plan 9 varies depending on what file system
you use. To illustrate, we can add a new user called bob, that is a member of
the email (upas) and admin groups (adm for user administration, sys for access
to system files), to various Plan 9 filesystems:
add user to a gefs file server
; mount /srv/gefs /adm adm
; sam /adm/users # edit by hand
; echo users >> /srv/gefs.cmd # refresh user db
; mount /srv/gefs /n/u %main # create user dir
; mkdir /n/u/usr/bob
; chgrp -u bob /n/u/usr/bob
; chgrp bob /n/u/usr/bob
optionally, set tmp so it doesn't take automatic snapshots
; echo snap -m main other >> /srv/gefs.cmd
; echo snap other retain '' >> /srv/gefs.cmd
; mount /srv/gefs /n/o %other
; mkdir -p bob /n/o/usr/bob/tmp
; chgrp bob /n/o/usr/bob^('' /tmp)
; chmod 700 /n/o/usr/bob^('' /tmp)
add user to a hjfs/cwfs file server
(replace hjfs.cmd with cwfs.cmd for cwfs server)
; echo newuser bob >> /srv/hjfs.cmd
; echo newuser upas +bob >> /srv/hjfs.cmd
; echo newuser adm +bob >> /srv/hjfs.cmd
; echo newuser sys +bob >> /srv/hjfs.cmd
add user to the auth server (on any filesystem)
; auth/keyfs
; auth/changeuser bob
; auth/enable bob
The new GEFS file system can be a little confusing if you are used to the old
way of doing things in 9front, the Version Control section below might help
explain what's going on. (and naturally, read the gefs(8) manpage) If you are
using a classic Plan 9 system, use fscons instead of hjfs.cmd/cwfs.cmd, and the
command uname rather then newuser, but otherwise it's the same as hjfs/cwfs.
The very first thing Bob needs to do when he first logs in to the Plan 9 box,
is to type /sys/lib/newuser. This will create an initial home directory with
basic files such as a lib/profile and a tmp directory. Why doesn't the system
do this by default? Consider it a security feature, users who aren't able to
type /sys/lib/newuser, have only limited access to the system in order to
protect the other users. Btw, you may wish to add the new user to secstore as
well (see section 7.4.3.1 in the fqa).
Security in Plan 9 is built around an astute observation; While it's the
operating systems responsibility to secure the digital world (ei. the network),
it is your responsibility, as a physical being, to provide physical security.
Like me, being a scrawny nerd, you may find that statement disconcerting.
Relax, don't get buffed, get smart: For example, if a Plan 9 network of
multiple diskless terminals, is serviced by a single file server, that isn't
also a CPU server; The only practical way to compromise file security on that
network, is to gain physical access to the file server machine. The sysadmin
can lock this machine behind a server room door, behind a death-ray enhanced
mutant shark pool, or whatever physical restraints his evil boss may fancy.
The user who boot's a machine has physical access to it. This hostowner owns
all the resources of that machine, but how much power that gives him on the
network depends entirely on how the network is configured. A Plan 9 machine
that isn't a CPU server, cannot be logged into remotely, a machine that isn't a
file server, cannot export its files, and a machine that isn't an auth server,
cannot authenticate remote transactions. In practice though, a 9front user will
typically set up his laptop as a self contained CPU+AUTH+File server, in which
case the hostowner is nearly as powerful as the Almighty root in UNIX.
(although he must still show ostensible respect for file permissions)
Single-user "terminals" on the other hand, where originally diskless, and do
not export any resources whatsoever. Thus they have nothing to secure and Plan
9 will let anyone login to such a machine without a password. This is not ideal
today, when a default Plan 9 installation provides a "terminal" with local disk
storage. There are a few ways to work around this issue: 1) Configure the
system to run as a CPU+AUTH server, which does require a password to login. 2)
Configure the BIOS to set up a boot password. 3) 9front allows you to encrypt
the harddisk, requiring a passphrase to log in (see section 4.4 in the fqa).
To demonstrate some multiuser shenanigans:
UNIX friendly aliases
fn su{
rcpu -u $*
}
fn chown{
chgrp -u $*
}
; su bob # switch user on CPU server
...
ERROR ERROR ERROR # Oops, bobs profile is missconfigured
...
for CWFS just change hjfs.cmd to cwfs.cmd
; echo allow >> /srv/hjfs.cmd # fs hostowner: allow chown
; chown glenda /usr/bob/lib/profile
; B /usr/bob/lib/profile # fix the problem
; chown bob /usr/bob/lib/profile
; su bob
GEFS works a little differently
; mount /srv/gefs /n/u %main # permissive mount point
; chown glenda /n/u/usr/bob/lib/profile
...
Disk Management
There is no df command in Plan 9 for measuring disk usage, but you can get that
information in other ways. On an hjfs file system run this command: echo df >>
/srv/hjfs.cmd. On cwfs and gefs the method is a bit awkward, you must first
write a command to the file server, and then read from the control file, eg:
echo statw >> /srv/cwfs.cmd && cat /srv/cwfs.cmd, will give you a bunch of
statistics for cwfs, currently using 16 Kb file system blocks (hit Del when you
are done) What you probably want is the last digit in the wmax line, which will
tell you how much percentage of the disk you are using (the cache line here is
also important, the cache is only 1/5 the size of the main storage area, but if
it runs out of space - you will run into problems!). gefs works similarly, but
gives you less statistics. Here is a crude df script for 9front that you may
find useful:
!/bin/rc
df - print disk usage
usage: df
if (test -f /srv/hjfs.cmd)
echo df >> /srv/hjfs.cmd
if (test -f /srv/gefs.cmd) {
echo df >> /srv/gefs.cmd
dd -if /srv/gefs.cmd -bs 1024 -count 12 -quiet 1
}
if (test -f /srv/cwfs.cmd) {
echo statw >> /srv/cwfs.cmd
dd -if /srv/cwfs.cmd -bs 1024 -count 21 -quiet 1 |
grep wmax | sed 's/.*\+//'
}
I think the method is similar to this in classic Plan 9, but I am not exactly
sure how to do this (feel free to drop me a line if you know how, or detect any
other deficiencies in my article for that matter). For individual files and
folders you can of course use the trusty old du command to measure their size.
Here is a simple and handy script that lists the files and folders in your
current directory sorted by disk usage:
!/bin/rc
dus - disk usage summary for current dir
usage: dus
du -s * | sort -nrk 1 | awk '{
if ($1 > 1073741824) printf("%7.2f %s\t%s\n", $1/1073741824, "Tb", $2)
else if ($1 > 1048576) printf("%7.2f %s\t%s\n", $1/1048576, "Gb", $2)
else if ($1 > 1024) printf("%7.2f %s\t%s\n", $1/1024, "Mb", $2)
else printf("%7.2f %s\t%s\n", $1, "Kb", $2)
}'
Of course, the whole point of Plan 9 is to centralize all the disk management
into one system, and access that from whatever devices you may be using on your
network. So invest in big disks and take the time to install a file server, and
don't ever worry about disk management again. (given that the 72 members of the
old UNIX crew managed to gobble up "34000 512-byte secondary storage blocks" in
5 years, a Terabyte harddisk should, mathematically speaking, hold you for 22
million years)* If you need to set up a complicated file server, you can
investigate the installer scripts an /bin/inst, to get a feel for how you would
go about doing such a setup manually. You can also check out the adventuresin9
YouTube channel, which has many good videos on this and other sysadmin
subjects. If you absolutely have to use Sneakernet, check ot the USB sticks
section below.
Backups
[spare_two]
Plan 9 file systems all have snapshot capabilities, so as long as the file
system itself is in working order, you can restore damaged or lost data without
much hassle. Of course, there is a big if here: The file system can get
damaged, and the machine it runs on can get damaged, and the building it lies
in can get damaged, and the country it lies in can get damaged, and the world
it lies in... you get the picture. So even if you have a super sophisticated
ultra safe file system with all the trimmings, it is not safe! You should
backup your data to an offsite location, preferably two offsite locations: If
an intruder compromises the data at one site, having two backups lets you
verify which data is accurate and which is corrupt.
The trick to migrating from the concept of backups to the practice of it, is
two fold. First, backups must be takes automatically. Doing backups manually
ensures that they don't get done. Secondly, only backing up essential files
will dramatically increase cost effectiveness. If you are an organized
individual, just write a proto(2) file for your important files, and schedule a
regular mkfs(8) job with cron(8). I however, am not an organized individual. My
first problem is that I boot my laptop only semi-regularly, so I need some easy
way to schedule a job "at least" once a day/week/month; If a weekly job hasn't
been run for a week or more when I boot my box, it needs to run again. Here is
a simple script that accomplishes this:
!/bin/rc
schedule - run commands at scheduled intervals
usage: schedule
depend: window schedule in $home/bin/rc/riostart
#
format: add commands to run in one of the following
files in $home/lib; daily, weakly, monthly.
set some defaults
rfork e
lock=$home/lib/lock
mkdir -p $lock
date=`{date}
datesec=`{date -n}
weekrun=Mon
daily=$home/lib/daily
weekly=$home/lib/weekly
monthly=$home/lib/monthly
check monthly scripts
if(test -f $monthly){
lockfile=monthly_$date(2)^_$date(6)
if(! test -f $lock/$lockfile){
rm -f $lock/monthly_*
touch $lock/$lockfile
@{rc $monthly}
}
}
check weekly scripts
if(test -f $weekly){
lockfile=weekly_$datesec
if(! test -f $lock/weekly_*) touch $lock/$lockfile
oldlockfile=`{ls -p $lock/weekly_*}
olddatesec=`{echo $oldlockfile | sed 's/weekly_//'}
oldweeksec=`{echo $olddatesec + 604800 | bc}
olddaysec=`{echo $olddatesec + 86400 | bc}
# by default run weekly scripts on a certain day,
# but make sure it runs at least once a week.
if(~ $date(1) $weekrun || test $datesec -gt $oldweeksec){
# also make sure it doesnt run twice in a single day
if(test $datesec -gt $olddaysec){
rm -f $lock/weekly_*
touch $lock/$lockfile
@{rc $weekly}
}
}
}
check daily scripts
if(test -f $daily){
lockfile=daily_`{date -i}
if(! test -f $lock/$lockfile){
rm -f $lock/daily_*
touch $lock/$lockfile
@{rc $daily}
}
}
respawn shell
rc
The script works by writing "lock" files with dates attached whenever a
scheduled job is executed. If these dates are older then a day/week/month (feel
free to expand the script to include quarterly/semily/yearly run jobs if you
wish), the job is executed again and the lock files are updated. Exactly how
you want to run schedule depends on your needs and tastes, but one suggestion
is to add window schedule to $home/bin/rc/riostart.
Now, to tackle my second problem: Just as time management in my life is
disorderly, so are my files. I know I have important stuff lying around
somewhere that I need to backup, but it's too much hassle finding out where.
Doing a full backup however is vastly inefficient, since my home directory
contains some non-textual nonsense. What I need is some quick way of saying
backup everything, except this and that. Here is one suggestion:
!/bin/rc
nom - no match, print all files except those given
usage: nom files...
rfork ne
temp=/tmp/nom-$pid
fn sigexit{ rm -f $temp }
if(~ $* */*){
echo 'nom quitting: can''t handle ''/''s.' >[1=2]
exit slash
}
ls -d $* > $temp
ls | comm -23 - $temp
exit # force file cleanup
!/bin/rc
backup - backup important files to offsite storage
usage: backup
rfork ne
backup semi-important files
mkdir -p /tmp/backup
fn copy{
mkdir -p $2
if (~ `{ls -ld $1} d*){
mkdir $2/$1
dircp $1 $2/$1
}
if not cp $1 $2
}
fn sigexit{ rm -rf /tmp/$backup /tmp/backup }
cd $home
for(file in `{nom bin doc games jw media pkg site tmp})
copy $file /tmp/backup
cd $home/bin
for(file in `{nom 386 amd64})
copy $file /tmp/backup/bin
cd $home/doc
for(file in `{nom books health os papers})
copy $file /tmp/backup/doc
backup=9front-^`{date -i}^.tar.gz
tar czf /tmp/backup /tmp/$backup
cd /tmp
PS: The first whitespace in sed here is a tab
md5sum $backup | sed 's/ / /' >> CHECKSUM
copy backup to offsite locations
fn sshcopy{
sshfs $1
if(! test -d /n/ssh/backup) {
echo Error: SSH failed!
exit ssh
}
cp /tmp/$backup /n/ssh/backup
cat /tmp/CHECKSUM >> /n/ssh/backup/CHECKSUM
}
sshcopy bkpserv1
sshcopy bkpserv2
exit # force file cleanup
Now the script here is very much tailored to my own idiosyncratic needs, so
don't just copy paste it! For example, I omit some big directories in $home,
such as media, where I pub all of that non-textual mess, and site where I keep
my web site. I do copy bin and doc, but only parts of them. Clearly, such
details, will not be relevant for your setup. But I hope the example might
inspire you to write a useful backup utility yourself. With these tools in
place, I can just add backup to $home/lib/weekly, and a weekly ~10 Mb backup of
my ~10 Gb* used diskspace is automatically taken, if I happen to boot my laptop
at least once a week. Of course, it's still useful to have a full tar czf $home
/n/ssh/backup/9front-full.tgz backup lying around, but running that command
manually once or twice a year suffice for my needs.
PS: If you happen to be a ZFS user, you may be yawning right about now. ZFS
does indeed have many fancy features that the Plan 9 file system lacks, but in
my humble opinion, the practicality of these features are overrated. For good
data security you need two offsite backups even with ZFS, and with such a
setup, additional data integrity and redundancy is somewhat overkill. Data
compression, not to mention deduplication, is even less relevant. With Terabyte
harddisks on commodity hardware nowadays we have infinite disk space, infinite
+50% extra is still infinite. Besides, if space were really such a premium,
redundancy would be evil. In any event, if you want self healing and all that
jazz in Plan 9 - and don't want to wait for GEFS to catch up - just backup your
files to a UNIX machine using ZFS (or better jet, run Plan 9 virtually from a
UNIX machine using ZFS).
ZFS primer for non-ZFS systems:
snapshots: yesterday
integrity: md5sum myfiles.tar.gz >> CHECKSUM
redundancy: cp myfiles.tar.gz /n/ssh/backup
compression: gzip myfile
encryption: auth/secstore -p myfile
replication: tar xzf myfiles.tar.gz
deduplication: <buy a disk man>
self healing: tel mysysadmin/man gefs
Package Management
[pack_it_in]
Plan 9 does not really have package management facilities in the sense that a
UNIX user would expect. The system is intended to be "fully-featured" (albeit
minimalistic) and few 3rd party software exists, those that do tend to be
distributed as plain source code requiring the user to compile them manually.
It has been toyed with some package management solutions for Plan 9, but for
the most part Plan 9 users usually just compile what they need by hand. Here
are a few examples to demonstrate what "package management" may entail in Plan
9:
PS: When compiling software in a Plan 9 terminal, remember to middle click the
window and select scroll. Otherwise the compilation will freeze once the output
has reached the bottom of the window (this is a "feature", not a bug).
Updating the 9front system - elaborately:
; sysupdate # download latest sources
; cd / # rebuild system
; . /sys/lib/rootstub
; cd /sys/src
; mk nuke # sometimes needed after library changes
; mk install
; mk clean
; cd /sys/man # optionally rebuild documentation
; mk
; cd /sys/doc
; mk
; mk html
; cd /sys/src/9/pc64
; mk install # optionally rebuild (64-bit) kernel
; 9fs 9fat
; rm /n/9fat/9bootfat
; cp /386/9bootfat /n/9fat
; chmod +al /n/9fat/9bootfat
; cp /amd64/9pc64 /n/9fat
; reboot # if you have installed a new kernel
Instruction for kernel installation is slightly different for 32-bit or arm,
see section 7.2.5 in the fqa for more info. Of course, you do not need to
reinstall the kernel and rebuild the docs for every minor update, usually all
you need to do is:
Updating the 9front system - quickly:
; sysupdate
; cd /sys/src
; mk install
Install xscreensaver package from the 9front extras:
; cd /tmp
; 9fs 9front # download package
; tar xzf /n/extra/src/xscr.tgz
; cd xscr # compile programs and install them
; mk
; for(f in 6.*){ mv $f $home/bin/$cputype/^`{echo $f | sed 's/6.//'} }
Install vim 7.1 port (old stuff):
; cd /tmp
; hget http://vmsplice.net/vim71src.tgz | gunzip -c | tar x
; cd vim71/src
; mk -f Make_plan9.mk install
Install the Bell-Labs port of perl (old stuff, 32-bit only):
; 9fs sources # download iso and mount it
; bunzip2 < /n/sources/extra/perl.iso.bz2 > /tmp/perl.iso
; mount <{9660srv -s >[0=1]} /n/iso /tmp/perl.iso
; cp /n/iso/386/bin/perl $home/bin/386 # install the binary
Install lua from git.sr.ht:
; cd /tmp
; git/clone https://git.sr.ht/~kvik/lu9
; cd lu9
; mk pull
; mk install
; lu9 script.lua # or interactively: lu9 -i
Install Scheme from Empty Space:
; cd /tmp
; git/clone https://github.com/bakul/s9fes
; cd s9fes
; mk
; mk inst
; s9 # do some scheming
Recompile 9front to amd64 and install golang:
go will only work on amd64 architecture, so if you are
running 386, rebuilt to 64-bit first:
; cd /
; . /sys/lib/rootstub
; cd /sys/src
; objtype=amd64 mk install
; cd /sys/src/9/pc64 # build and install a 64-bit kernel
; mk install
; 9fs 9fat
; rm /n/9fat/9bootfat
; cp /386/9bootfat /n/9fat
; chmod +al /n/9fat/9bootfat
; cp /amd64/9pc64 /n/9fat
; sam /n/9fat/plan9.ini # make sure bootfile=9pc64 (not 9pc!)
; reboot # reboot to a 64-bit system, download Go stuff
now, lets build go, we will bootstrap the latest version
of go from 9legacy, then use that to build the go source
(these instructions quickly get outdated):
; mkdir /sys/lib/go
; cd /sys/lib/go
; hget http://www.9legacy.org/download/go/go1.23.6-plan9-amd64-bootstrap.tbz |
; bunzip2 -c | tar x
; hget https://golang.org/dl/go1.23.6.src.tar.gz |
; gunzip -c | tar x
; mv go amd64-1.23.6
; GOROOT_BOOTSTRAP=/sys/lib/go/go-plan9-amd64-bootstrap
; GOROOT=/sys/lib/go/amd64-1.23.6
; cd amd64-1.23.6/src
; make.rc
; bind -b $GOROOT/bin /bin
get some recent certificates
; hget https://curl.haxx.se/ca/cacert.pem > /sys/lib/tls/ca.pem
; go get golang.org/x/tools/cmd/godoc
to make the go environment permanent, add these
instructions to your $home/lib/profile
; GOROOT=/sys/lib/go/amd64-1.23.6
; bind -b $GOROOT/bin /bin
PS: In classic Plan 9, you would run replica/pull /dist/replica/network to get
the latest sources from Bell Labs, and 9fs sources to get the Bell Labs
repository of contributory software listed under /n/sources. Today however,
these resources are gone. You can still mount a snapshot of the contrib
repository in 9legacy by running the command srv -nqC tcp!9p.io sources /n/
sources, the official 3rd party software from Bell Labs will be in /n/sources/
extra, while the repository of contributors are in /n/sources/contrib. You can
also manually mount the Bell Labs wiki from 9p.io like so: srv -m 'net!9p.io!
wiki' wiki /mnt/wiki, you can then access the wiki by running acme /acme/wiki/
guide, and follow the instructions there (in 9front accessing these resources
are done with: 9fs sources and 9fs wiki). Note however that these old resources
are in no way maintained, so they are more of archaeological, then practical,
interest. Concerning 9front specific scripts and programs, many of them may
work just fine in 9legacy, or any other classic Plan 9 system. Feel free to try
it out :)
File Management
The default "file manager" in Plan 9 is acme. If you run B path/mydir for
instance, the contents of mydir will be listed in acme.* Right clicking on a
directory here will list its contents, clicking on a text file will open it up
for editing, and clicking a PDF or audio file will open it up in page or play,
and so on. To do file operations, just type in the commands and execute them,
eg. type and middle click touch myfile to create myfile.
You can of course use the shell to manage your files, but there are a few
differences between UNIX and Plan 9 that might trip you up. For example, you
don't have rmdir, just use rm to delete your directories. Also there is no cp
-r, instead you have dircp that copies directories. So, if you need to copy
mydir to otherpath, you need to run mkdir otherpath/mydir; dircp mydir
otherpath/mydir. If you only want to copy the content of mydir, not the
directory itself however, just run dircp mydir otherpath. This may seem
cumbersome to a UNIX user, but it does actually have some benefits. Beyond a
simpler implementation, the approach is unambiguous. I do not know how many
times I have run cp -r mydir otherpath in UNIX, when I actually meant to run cp
-r mydir/* otherpath (ei. I only wanted to copy the contents of mydir). In Plan
9 you don't have this problem.
Lastly, if you really want a GUI, there is a nice 3rd party file manager,
called vdir. It works much like the acme file manager, you right click on
things to open them up.
[filemng]
Tips for UNIX Sysadmins
As the previous section illustrates, there are some fundamental differences
between UNIX utilities, and Plan 9 equivalents. A good UNIX to Plan 9
translation of various sysadmin commands are given here. You will note that
many essential tools that *nix graybeards take for granted, such as find or top
, are not available in Plan 9. And naturally, standard tools may not work as
you expect either, the shell does not Tab auto-complete, cp does not copy
recursively, ls does not columnize its output, and so on. This can be very
unsettling for seasoned UNIX veterans, but don't panic, the Plan 9 way of doing
things will make sense if you give it time. Incidentally, walk (or even du -a)
can be used as a lightweight alternative to find, pstree, memory and winwatch
should help you monitor your programs, Ctrl-f auto-completes filenames in the
shell, and as we have seen, dircp copies recursively and lc lists files in
multiple columns. You can usually reach your goal in Plan 9, you just have to
learn to walk a different path...
Digression on find and locate
[messy]
UNIX powerusers love the find command, and in modern times more and more UNIX
utilities come the -R flag. In the olden days such features were righfully
dreaded by sysadmins due to their prohibitive resource cost, and you'll find
that most of the Plan 9 utilities (eg. grep, chmod) do not support recursion.
This is quite vexing for new users, but you can do recursive jobs in Plan 9, by
manual for loops or by using the walk command:
Method 1: du (simplistic)
; time du -a $home | grep plan9_desktop.html
367 /usr/dan/site/blog/2019/plan9_desktop.html
0.41u 0.32s 1.14r
Method 2: walk or, without recursion, ls (complex)
; time walk $home | grep plan9_desktop.html
/usr/dan/site/blog/2019/plan9_desktop.html
0.13u 0.21s 0.62r
; walk -exp | grep rwxr-xr-x # find . -perm 755
; ls -l | grep rwxr-xr-x
; walk -d # find . -type d
; ls -F | grep '/$'
; walk -eUp | grep myname # find . -user myname
; ls -l | awk '$4=="myname"'
; walk -esp | awk '$1 > 1000000' # find . -size +2000 (+1M)
; du -a | awk '$1 > 1000000'
; walk -emp | awk '$1<'`{date -n}^'-(30*3600*24)' # find . -mtime +30
; date=`{date} ; ls -l | grep -v $date(2)
; walk -emp | sort -n | sed /file/q # find . -newer file
; ls -t | sed /file/q
find . -atime +5 \( -name "*.out" -o -name "*.tmp" \) -print
; walk -eap | awk '$1<'`{date -n}^'-(5*3600*24) && $2 ~ /^*.(out|tmp)$/'
find . -type d -a -exec chmod 771 {} \;
; chmod 771 `{walk -d}
grep -R pattern
; grep pattern `{walk -f}
Method 3: makeshift locate (fast)
; time locate plan9_desktop.html
/usr/dan/site/blog/2019/plan9_desktop.html
0.04u 0.00s 0.05r
list files taking 100+ Mb of space
; locate -s '^[0-9][0-9][0-9][0-9][0-9][0-9]+' | sort -n
104895 /usr/dan/doc/os
109058 /usr/dan/site/images
...
list files under site/blog
; locate -s site/blog | awk '{ print $2 }'
/usr/dan/site/blog/2019/thumbs/tiling.png
/usr/dan/site/blog/2019/thumbs/rio.png
...
; cat $home/bin/rc/locate
!/bin/rc
locate - search for files by name or size
usage: locate -u | [-i|-s] file
set defaults and check for errors
rfork ne
if (~ $#* 0) {
sed -n '/^# usage/s/# u/U/p' $0 >[1=2]
exit usage
}
db=$home/lib/locate
flag=()
-u update database and quit
if(~ $1 -u){ du -a /root | sed 's/\/root//' | tr -d '''' > $db; exit }
-i case insensitive search, -s search verbatim (eg. file size)
if(~ $1 -i){ flag=-i && shift }
if(~ $1 -s){ grep $flag $2 $db }
if not grep $flag $1 $db | grep $flag -v ''$1'[^/]+/' | awk '{ print $2 }'
The performance of these three methods are eerily similar to the UNIX
equivalents, du, find and locate. I actually use the inefficient du/grep method
all the time, due to muscle memory and sheer sloppiness. And as long as you
aren't searching through something ridiculous, like $home, you won't notice the
lag.
But for complex searches walk in invaluable. UNIX graybeards should beware
though; walk is not an attempt at porting find to Plan 9, it is meant to be a
kind of recursive ls. The good thing is that walk is easy to learn, once you're
familiar with the capabilities of ls you're all set. (the letters following the
-e flag specifies what file attributes to list, eg. walk -exTDUGsmp is
essentially a recursive ls -l, see ls(1) and walk(1)) But for complex jobs you
may need to intermix this command with awk and other tools. In the -mtime and
-atime examples above, we are calculating the number of seconds we need to go
from current time since epoch (date -n). Whenever things get this complicated,
it's a good idea to pause for a minute and reflect on how we can streamline our
filesystem. (if the time factor is that important we might put it in the
filename for instance?) Of course, if you find yourself using complex walk
commands often, it could be useful to create some aliases, eg:
fn ff{ walk -f -exTDUGsmp | grep $* } # find file
fn rgrep{ grep $* `{walk -f} }
fn bigfiles{ walk -f -esp | awk '$1>100000000' } # 100+ Mb
fn nullfiles{ walk -f -esp | grep '^0' }
fn duplicates{ walk -f -enp | sort -k 1 | awk '
{ if($1==dup) printf("%s\n%s\n", old, $2); dup=$1; old=$2 }'}
fn today{walk -emp | awk '$1>'`{date -n}^'-(3600*12)'}
fn week-{walk -emp | awk '$1>'`{date -n}^'-(7*3600*24)'}
fn week+{walk -emp | awk '$1<'`{date -n}^'-(7*3600*24)'}
Finally, we wrote a makeshift locate implementation using du. The -s flag allow
you to search the database verbatim, which we can exploit to search for file
size or directory contents. This final method is fast, but it requires you to
periodically update the database with locate -u. (tip: do it weekly with a cron
or schedule job)
We could make our locate script orders of magnitude faster by using look
instead of grep, eg: awk '{ path=$2; sub(/.*\//, "", $2); print $2, $1, path }'
$home/lib/locate | sort > $home/lib/fastlocate; look myfile $home/lib/
fastlocate But I do not recommend it. This technique has several disadvantages,
inflexible searching for one, and it won't save you time in practice unless you
routinely search through billions of files.*
PS: Classic Plan 9 does not have walk. Of course, you can construct a shell
script that walks the directory tree and ls the directories for information,
but it's not exactly pretty.
Quick CPU+AUTH+Qemu+Drawterm HOWTO
[snappy]
As mentioned at the onset, the focus of this guide is on using Plan 9 as a
day-to-day desktop, not installation and configuration. So I really didn't want
to to do this... but I suppose it's unavoidable. The problem here is that there
are just so many variables when setting up a Plan 9 CPU server. For example, do
you run the machine on bare metal or virtually, if virtually what virtual
machine do you use, in what operating system, if Linux, what distro... And we
haven't even begun to consider the many different ways you can configure Plan 9
itself! What I present here then is just a quick howto. I assume you want to
install a Plan 9 CPU server in qemu on a Linux, or other UNIX, machine, and
that you go with all of the default options during the installation of Plan 9,
and that you say "y" to all yes or no questions. I will not explain indepth the
steps we take here, and I gloss over details that are unimportant. But if you
follow the instructions carefully, you will end up with a drawterm connected to
a Plan 9 CPU+AUTH server running in qemu, well... at least on my machine ;)
To make this work, we need to use some painfully detailed qemu flags, so I
recommend using the following wrapper script to launch qemu:
!/bin/sh
9qemu: wrapper script for launching Plan 9 in qemu
usage: 9qemu disk [args...]
disk=$1 && shift
if [ $(uname -s) = Linux ]; then
# non-linux systems may not have this
kvm=-enable-kvm
fi
flags="-net nic,model=virtio,macaddr=52:54:00:12:34:56 \
-net user,hostfwd=tcp::17010-:17010,hostfwd=tcp::17019-:17019,\
hostfwd=tcp::17020-:17020,hostfwd=tcp::12567-:567 \
-device virtio-scsi-pci,id=scsi -device scsi-hd,drive=vd0 \
-device sb16 -vga std -drive if=none,id=vd0,file=$disk"
qemu-system-x86_64 $kvm -m 2G $flags $*
9front
You have three file systems to choose from in 9front, generally, cwfs (the
default) is fast and stable but requires a large disk (+30 Gb), hjfs is super
simple but a bit slow and gefs is new and cool but not entirely settled yet. I
would recommend hjfs for beginners, but if you prefer the default cwfs file
system, follow the steps that are commented out: (as for gefs the setup is
virtually the same as hjfs)
Step 0: install qemu and drawterm (9front edition)
$ sudo apt install qemu # adjust to suit your system
$ firefox https://drawterm.9front.org # download drawterm
$ tar xzf drawterm-*.tar.gz
$ cd drawterm-*
$ CONF=linux386 make # adjust to suit your system
$ cp drawterm $HOME/bin
Step 1: install 9front and reboot
$ qemu-img create -f qcow2 9front.img 2G
qemu-img create -f qcow2 9front.img 30G # cwfs needs a big disk!
$ 9qemu 9front.img -cdrom 9front.iso -boot d # use hjfs file system!
$ 9qemu 9front.img
Step 2: configure boot
; 9fs 9fat
; sam /n/9fat/plan9.ini
change bootargs and add this:
bootargs=local!/dev/sd00/fs -m 448 -A -a tcp!*!564
nobootprompt=local!/dev/sd00/fs -m 448 -A -a tcp!*!564
for GEFS:
bootargs=local!/dev/sd00/fs -A -a tcp!*!564
nobootprompt=local!/dev/sd00/fs -A -a tcp!*!564
for CWFS:
bootargs=local!/dev/sd00/fscache -a tcp!*!564
do not set nobootprompt yet for cwfs!
user=glenda
auth=10.0.2.15
cpu=10.0.2.15
authdom=virtual
service=cpu
Step 3: write nvram and add user
; auth/wrkey
authid: glenda
authdom: virtual
secstore key: ******
password: ******
; auth/keyfs
; auth/changeuser glenda
password: ******
post id: glenda
Step 4: configure network
; sam /lib/ndb/local
change last line and add this:
sys=cirno ether=525400123456 authdom=virtual auth=10.0.2.15 ip=10.0.2.15
ipnet=qemu ip=10.0.2.0 ipmask=255.255.255.0
ipgw=10.0.2.2
auth=10.0.2.15
authdom=virtual
fs=10.0.2.15
cpu=10.0.2.15
dns=8.8.8.8
Step 5: configure startup
; sam $home/lib/profile
add these lines at the end of the cpu section, before "case con":
if (test -d /mnt/term/dev) {
# cpu call from drawterm
webfs
plumber
rio -i riostart
}
reboot
; fshalt -r
Step Z: enable auth services for cwfs, you only need to do this if you
used the cwfs file system rather then hjfs during installation (ps: you
may want to set nobootprompt in plan9.ini after this):
bootargs:local!/dev/sd00/fscache -c
config: noauth
config: noauth
config: end
Connecting to the server with drawterm:
$ drawterm -a 'tcp!localhost!12567' -s localhost -h localhost -u glenda
PS: if you are having trouble with the auth/wrkey command, getting an error
about nvram being an unknown device, you may need to tell Plan 9 where to find
it. Eg. ls /dev/*/nvram might return /dev/sdN0/nvram. Armed with that knowledge
you can add this to plan9.ini: nvram=/dev/sdN0/nvram
9legacy
As you will see, setting up a 9legacy CPU+AUTH server is notably different from
9front. Classic Plan 9 has also a few issues with qemu, first of all, Plan 9
from Bell Labs does not recognize the harddisk with this setup, although
9legacy does. The fshalt script in the original Plan 9 system does not work
right in qemu, which is why we make our own halt script in this example.
Finally, graphics do not work with this setup. This isn't a huge deal (unless
you hate ed), since we can connect to the CPU server with a graphical drawterm
once things have been configured. However, if you just want to quickly install
9legacy and play around in the desktop without drawterm, run something like
this instead: qemu-systex-x86_64 -m 2G -hda 9legacy.img PS: To avoid a naming
conflict with the 9front drawterm, we call the original version of drawterm "
9drawterm".
Step 0: install qemu and drawterm (original plan9 edition)
$ sudo apt install qemu # adjust to suit your system
$ firefox https://github.com/9fans/drawterm # download drawterm
$ unzip drawterm-master.zip
$ cd drawterm-master
$ CONF=unix make
$ cp drawterm $HOME/bin/9drawterm
Step 1: install 9legacy and reboot
$ qemu-img create -f qcow2 9legacy.img 2G
$ 9qemu 9legacy.img -cdrom 9legacy.iso -boot d
PS: choose /dev/sdD0/data as the distribution source, type exit at the
/% prompt, and choose plan9 as the boot method.
$ 9qemu 9legacy.img
Step 2: Do some initial configurations
; echo uname adm +glenda >>/srv/fscons
; cp /adm/timezone/GMT /adm/timezone/local # adjust to suit your needs
; mv /cfg/example /cfg/gnot
; echo ip/ipconfig >> /cfg/gnot/cpurc
; echo aux/listen -q -t /rc/bin/service.auth -d /rc/bin/service tcp >> /cfg/gnot/cpustart
; mv /rc/bin/service.auth/authsrv.tcp567 /rc/bin/service.auth/tcp567
; echo fsys main create /active/cron/glenda glenda glenda d775 >>/srv/fscons
; echo fsys main create /active/sys/log/cron glenda glenda a664 >>/srv/fscons
; ed /rc/bin/cpurc
g/^# auth/s/# (auth.+)/\1/
w
q
Step 3: configure network
; ed /lib/ndb/local
$
a
sys=gnot ether=525400123456 authdom=virtual auth=10.0.2.15 ip=10.0.2.15
ipnet=qemu ip=10.0.2.0 ipmask=255.255.255.0
ipgw=10.0.2.2
auth=10.0.2.15
authdom=virtual
fs=10.0.2.15
cpu=10.0.2.15
dns=8.8.8.8
.
w
q
; ed /lib/ndb/auth
$
a
hostid=glenda
uid=!sys uid=!adm uid=*
.
w
q
Step 4: rebuild kernel
; cd /sys/src/9/pc
; mk 'CONF=pccpuf'
; 9fat:
; cp 9pccpuf /n/9fat
; ed /n/9fat/plan9.ini
/9pcf/s/9pcf/9pccpuf/
w
q
Step 5: Setup nvram and users
; auth/wrkey
authid: glenda
authdom: virtual
password: ******
; auth/keyfs
; auth/changeuser glenda
password: ******
post id: glenda
Step 6: Halt system and reboot
PS: the classic fshalt script doesn't work in qemu
; ed /rc/bin/halt
a
!/bin/rc
echo fsys main sync >>/srv/fscons
sleep 5
echo Its now safe to turn off your computer
echo fsys main halt >>/srv/fscons
.
w
q
; chmod +x /rc/bin/halt
; halt
click Machine -> Reset in qemu when its safe to reboot
Connecting to the server with (the original) drawterm:
$ 9drawterm -a 'tcp!localhost!12567' -s localhost -c localhost -u glenda
CPU+Rio desktop
By default a CPU server in Plan 9 does not run a graphical desktop, the
original intention was that this machine would service a number of diskless
single-user remote desktops ("terminals") on the network. If you set up your
laptop as a self contained CPU+AUTH server however, you almost certainly want
to use it interactively! To do so, you can investigate the difference between /
bin/termrc and /bin/cpurc, the scripts that configure the system to run as
either a "terminal" or a CPU server. In 9front for instance, you can add this
to /cfg/<mymachine>/cpustart to enable a graphical desktop on the CPU server
<mymachine>:
aux/realemu
aux/vga -m vesa -l 1600x900x32 # screen dependent
bind -a '#m' /dev
aux/mouse ps2 # mouse dependent
for(i in v m i A) # add extra devices
bind -a '#'^$i /dev >/dev/null >[2=1]
rc $home/lib/profile # regular user setup
You may also need to tweak $home/lib/profile so that rio etc starts on both the
cpu server and from drawterm connections. (if the initial setup doesn't start
the desktop, just run rio manually and fix your profile)
For 9legacy the specifics are a little different, although you use the same
method. It is a mute point however, since a 9legacy CPU server cannot run
graphics in a virtual machine (in my experiments at least), and it is unlikely
that you'll be able to run such a system on bare metal.
CPU+PXE terminals
Personal computing, and other fads, aside, it is possible to run a Plan 9
network with multiple diskless workstations, as God intended. With minor tweaks
you can follow the above instructions, and install a CPU+AUTH+File server on
real hardware. Once that is up and running, you only need a few additional
tweaks to pxe boot diskless workstations. First, enable the dhcp and tftp
daemons on the server, by adding these lines to /cfg/<mymachine>/cpurc:
ip/dhcpd
ip/tftpd
Then configure the network to use these services, by adding the following lines
in the ipnet tuple in /lib/ndb/local:
...
ipnet=qemu ip=10.0.2.0 ipmask 255.255.255.0
...
dns=10.0.2.15
dnsdomain=qemu
tftp=10.0.2.15
add a line for each pxe booted client
sys=term1 dom=term1.qemu ether=8c1645bac636 ip=10.0.2.101 bootf=/386/9bootpxe
...
We use the authdom and dnsdomain "qemu" here, which is a rather daft name if we
intend to do a physical installation. It doesn't actually matter what label we
give it though, as long as it uniquely identifies the auth server. The ether
line here is the MAC address of the diskless workstation we want to pxe boot.
Finally, we must provide a plan9.ini file for our client in /cfg/pxe/<MAC
address> (/cfg/pxe/8c1645bac636 in our case):
bootfile=/amd64/9pc64
bootargs=tls
nobootprompt=tls
auth=10.0.2.15
fs=10.0.2.15
mouseport=ps2intellimouse
monitor=vesa
vgasize=1920x1080x32
user=dan
If we now reboot our server, connect an ethernet cable to the client and
configure its BIOS to boot via the network, everything should work fine (if
not, section 6.7 in the 9front fqa, might provide some help). Of course we can
mangle our network further in infinite ways: We could run the CPU, AUTH and
File server on separate machines, and we can have more then one CPU/File
server. We could also do all of this virtually, or a mix of virtual and bare
metal configurations, including using UNIX as an emulated 9P server. Feel free
to experiment!
Automation
[mekonta]
What is the fundamental value of a computer? However controversial it may be to
say so, it is not watching skaters trip over themselves on Youtube, or emailing
cute cat photos to your colleagues. The fundamental value of a computer is
automation. Just as a tractor allows you to plow a field with much less effort
then a shovel would, so a computer allows you to do your monthly accounting
with much less effort then pen and paper. So the question of how to use a
computer efficiently and wisely, boils down to programming it to do your
chores. Now I know what you are thinking, but relax. There is "programming",
and then there is programming, we are only going to cover the first topic here,
and leave the latter for the professionals ;)
Shell Scripting
Plan 9's shell, rc is heavily inspired by the classic UNIX shell, sh (the
Bourne Shell). Nevertheless it is a complete rewrite and behaves quite
differently. One obvious difference is the syntax. The original UNIX shell was
designed to mimic the syntax of a user-friendly programming language called
ALGOL. In retrospect this was undeniably a mistake. rc however mimics the C
syntax, which makes a lot more sense, since this is the programming language
used elsewhere in the system.
Another big difference is that sh treats everything as a string, support for
arrays were added later. This means that correct quoting is super important in
the UNIX shell, and arrays are clunky. The Plan 9 shell on the other hand
treats everything as a list, so arrays are seamless. Quoting is also simpler
since there is only one escape character (single quotes).
You will find several rc scripts in this article that demonstrate it's use, but
here is a short list of sh to rc translations (like C, curly brackets in rc are
somewhat optional):
UNIX SHELL PLAN 9 SHELL
mesg="Hello World" mesg=(Hello World)
echo "$mesg's!" echo $"mesg'''s!'
echo ${a}string echo $"a^string
rm *.{mp3,ogg} rm *.^(mp3 ogg)
echo date: `date` echo date: `{date}
list=(`ls`) list=`{ls}
echo 1st: ${list[0]} echo 1st: $list(1)
echo all: ${list[@]} echo all: $list
echo num: ${#list[@]} echo num: $#list
echo 2>/dev/null echo >[2]/dev/null
echo >/dev/null 2>&1 echo >/dev/null >[2=1]
if [ "$1" = yes ]; then if(~ $1 yes){
echo hi echo hi
else } if not {
echo bye && exit 1 echo bye && exit bye
fi }
echo err: $? pid: $$ echo err: $status pid: $pid
while true; do while(){
(subproc) @{subproc}
done }
for i in "$@"; do for(i in $*){
echo ${i%.*} echo $i | sed 's/\..*//'
echo $(($i + 1)) echo $i + 1 | hoc
let j++ j=`{echo $j + 1 | hoc}
done }
case in "$@"; do switch($*){
a) echo Abe case a
;; echo Abe
b) echo Bob case b
;; echo Bob
*) echo Who? case *
;; echo Who?
esac }
alias l='ls -l' fn l{ ls -l }
f(){ fn f{
echo Funky! echo Funky!
} }
Many short scripts in this article are written as functions, this is because I
usually add them to a custom alias file, as mentioned in the configuring
startup and shutdown section. But you can easily rewrite these functions as
standalone shell scripts if you want.
Rio Scripting
The desktop in Plan 9 is fully scriptable, and in true UNIX fashion, you
control it by using a file interface.
For example, if you only have one window open, and run the command ls /dev/wsys
/wsys, you should see something similar to this: /mnt/wsys/wsys/1/ This tells
you that there is only one window currently open, which has the ID 1.
Now run the command echo new sam > /mnt/wsys/wctl, this should open up a new
sam window. If you ls the /mnt/wsys/wsys directory again, you should see two
windows listed. You can now delete the sam window with the command echo delete
/mnt/wsys/wsys/2/wctl, assuming that your sam window had the ID 2. To resize
the first terminal window, either run echo resize -r 0 0 1360 1080 > /mnt/wsys/
wsys/1/wctl, or more simply echo resize -r 0 0 1360 1080 > /dev/wctl.
• /dev/wctl window control file for the current window
• /mnt/wsys/wctl window control file for the system
• /mnt/wsys/wsys/n/wctl window control file for window n
rio also provides other files that you can use to control its interface, some
of these are discussed in the manipulating text in the terminal and taking a
screenshot sections. For all of these files, the ones in /dev refer to your
current window, use /mnt/wsys/wsys/n/ to manipulate another window. Here is the
full list of files that rio provides:
• cons the console
• consctl the console control file
• kbd raw keyboard events
• cursor appearance of the mouse cursor
• label the window label
• mouse raw mouse input
• screen image of the screen
• snarf the snarf buffer, or "clipboard"
• text copy of the window text
• wctl the window control file
• wdir the current working directory
• winid the window ID number
• window image of the window
• wsys a subdirectory containing the other windows in rio
The fact that the window manager can be easily scripted with standard shell
tools gives it enormous flexibility. Just a quick example to wet your appetite:
The following command will print the window ID number for each window on the
screen: for (win in /mnt/wsys/wsys/*) cat $win/winid > $win/cons (if you only
want to print ID's on visible windows use this command: for (win in /mnt/wsys/
wsys/*) if (dd -if $win/wctl -bs 128 -count 1 -quiet 1 | grep -s visible) cat
$win/winid > $win/cons) PS: If you just want to quickly get the window id of
some specific application, say acme, you can just grep for it: grep acme /mnt/
wsys/wsys/*/label.
Scrambling and Unscrambling a Rio Screen
As mentioned in the manipulating text section, text written to the system
console will appear directly on your screen. This can be seriously annoying,
especially if you have buggy hardware, which can make the kernel spam error
messages that clutter upp your screen. To automatically ignore all such
messages, you can add this line: window -hide -scroll cat /dev/kprint to $home/
bin/rc/riostart. You may also find the following script helpful, it basically
redraws the active rio windows, and thus unscrambles the screen:
!/bin/rc
unscramble - clear up a garbled rio screen
usage: unscramble
rfork e
screensize=(`{echo $vgasize | sed 's/x/ /g'})
window -r 0 0 $screensize(1) $screensize(2) exit
for (win in /mnt/wsys/wsys/*) {
if(dd -if $win/wctl -bs 128 -count 1 -quiet 1 | grep -s visible){
echo hide > $win/wctl
echo unhide > $win/wctl
}
}
[scramble]
Of course if you happen to be a weird cookie such as myself, you may actually
enjoy scrambling the screen on purpose. We have already seen examples of how
this feature provides an easy notification mechanism, but you can abuse it in
other ways as well. For instance, I have a bat script that draws an ASCII bat
signal on the system console and plays the batman theme song. It totally messes
up the display, and is a nice facepalmer if I happen to mistype batt, mentioned
in the battery monitoring section above. Here is another, more "useful"
example. The script draws a fast moving fullscreen stats display, and then
garbles it up at regular intervals. I find it sufficiently newb repellent to
work as a de facto screen locker. A non-Plan 9 user (aka everyone) who sees
such a screen, will assume that the computer is horribly broken somehow and
refuse to touch it with a ten foot pole. Of course, once you delete the stats
window everything will return back to normal.
!/bin/rc
scramble - garbles up a rio screen
usage: scramble
rfork e
fn sigexit{ kill stats | rc }
screensize=(`{echo $vgasize | sed 's/x/ /g'})
window -r 0 0 $screensize(1) $screensize(2) stats -T 0.01 -cflmsz &
while(sleep 3){
if (! ps | grep -s stats) exit
dd -if /dev/random -of '#c/cons' -bs 1024 -count 1 -quiet 1
}
max - Maximizing Windows
The following script lets you maximize windows in various ways, eg. max will
make your current window fullscreen, and max u will restore its previous
dimensions. max r 2 will place window with ID 2 on the right half of the
screen, and so on, enjoy!
!/bin/rc
max - maximize windows
usage: max [orientation] [winid]
#
orientation can be: f (fullscreen), l (left), r (right), t (top), b (bottom),
tl (top-left), tr (top-right), bl (bottom-left), br (bottom-right) or u
(unmaximize), default is fullscreen.
#
bugs: if you are maximizing another window, orientation is required
unmaximize is only useful right after maximizing a window.
set some defaults
screensize=(0 0 `{echo $vgasize | awk -Fx '{ print $1, $2 }'})
if(~ $#windowsize 0)
windowsize=`{dd -if /dev/window -bs 1 -count 70 -quiet 1 |
awk '{ print $2, $3, $4, $5}'}
window=/dev/wctl
if(~ $#* 0) echo resize -r $screensize > $window
if(~ $#* 2) window=/mnt/wsys/wsys/$2/wctl
if(test $#* -gt 2){
echo usage: max [orientation] [winid] >[1=2]
exit
}
maximize window
echo current > $window
switch $1 {
case f
echo resize -r $screensize > $window
case l
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $3, $4, $5/2, $6) }' > $window
case r
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $5/2, $4, $5, $6) }' > $window
case t
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $3, $4, $5, $6/2) }' > $window
case b
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $3, $6/2, $5, $6) }' > $window
case tl
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $3, $4, $5/2, $6/2) }' > $window
case tr
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $5/2, $4, $5, $6/2) }' > $window
case bl
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $3, $6/2, $5/2, $6) }' > $window
case br
echo resize -r $screensize |
awk '{ printf("%s %s %d %d %d %d", $1, $2, $5/2, $6/2, $5, $6) }' > $window
case u
echo resize -r $windowsize > $window
windowsize=()
}
ws - Multiple Workspaces
This script provides a virtual workspace-like service for rio. You use it by
typing ws n, where n is an arbitrary workspace number. The script works by
registering which windows belongs to which "workspace", and then automatically
hides or unhides the correct windows as you "switch" between them. Of course
this is only a pseudo-virtual workspace, all the windows are still available in
the rio menu, and plumbing a file in one "workspace" may open the file in a
different "workspace". I recommend riow mentioned in the workspaces section
above, for a better end user experience, but the following script might provide
some useful insights.
!/bin/rc
ws - pseudo virtual workspaces for rio
usage: ws n
#
bugs: the ws workspaces are not isolated from each other, if you need
that open a fullscreen window in each ws workspace and run
plumber followed by rio in it. even then rio is still blissfully
unaware of such "workspaces".
set some defaults
rfork ne
tmp=/tmp/ws
winid=`{cat /dev/winid}
initialize 1st desktop on first run
if(! test -d $tmp){
mkdir -p $tmp
touch $tmp/1
echo 1 > $tmp/currentws
ls -np /mnt/wsys/wsys > $tmp/`{cat $tmp/currentws}
}
update window lists
ls -np /mnt/wsys/wsys > $tmp/riowindows
cat $tmp/[0-9]* | sort -n > $tmp/wswindows
comm -23 $tmp/riowindows $tmp/wswindows >> $tmp/`{cat $tmp/currentws}
for(i in `{comm -13 $tmp/riowindows $tmp/wswindows}){
for(w in $tmp/[0-9]*) sed '/^'$i'$/d' $w > $tmp/TMP && mv $tmp/TMP $w
}
currentws=`{cat $tmp/currentws}
no args: echo current ws (after updating windows) and exit
if(~ $#* 0){ echo $currentws && exit }
touch $tmp/$1
switch desktop
if(~ $1 $currentws){ echo this is workspace $1 && exit }
for(i in `{cat $tmp/`{cat $tmp/currentws} | sed '/^'$winid'$/d' })
echo hide > /mnt/wsys/wsys/$i^/wctl
echo $1 > $tmp/currentws
for(i in `{cat $tmp/`{cat $tmp/currentws}}) echo unhide > /mnt/wsys/wsys/$i^/wctl
echo hide > /mnt/wsys/wsys/$winid^/wctl
tile - Tiling Window Manager
tile will auto arrange your windows in a tiling fashion. The algorithm is
simple, place one window on the left half of the screen, then carve up the
right half in even slices for the remaining windows. The script is
intentionally basic, so feel free to expand or adjust it to suit your own
needs.
!/bin/rc
tile - tile windows
usage: tile
gather some information
rfork e
screensize=(0 0 `{echo $vgasize | awk -Fx '{print $1, $2}'})
windows=`{for (win in /mnt/wsys/wsys/*)
if(dd -if $win/wctl -bs 128 -count 1 -quiet 1|grep -s visible)
echo `{basename $win}
}
fn left{awk '{printf("%s %s %d %d %d %d",$1,$2, 0, 0,$5/2,$6 )}'}
fn right{awk '{printf("%s %s %d %d %d %d",$1,$2,$5/2,'$b',$5, '$e')}'}
auto tile windows
if(~ $#windows 1)
echo resize -r $screensize > /mnt/wsys/wsys/$windows/wctl
if not {
echo current > /mnt/wsys/wsys/$windows(1)^/wctl
echo resize -r $screensize | left > /mnt/wsys/wsys/$windows(1)^/wctl
windows=`{echo $windows | sed 's/^[^ ]+ //'} # shift windows
step=`{ echo $screensize(4) / $#windows | bc }
b=0; e=$step # begin, end
for(i in $windows){
echo current > /mnt/wsys/wsys/$i/wctl
echo resize -r $screensize | right > /mnt/wsys/wsys/$i/wctl
b=`{ echo $b + $step | bc }
e=`{ echo $e + $step | bc }
}
}
Acme Scripting
In the above section several window manager scripts are demonstrated, but if
you middle click tile, or any of the other window manager scripts, in acme,
nothing will happen. The reason for this is that the namespace of a terminal
window, and acme, are different. If you middle click win and look around in /
dev and /mnt you will see that these directories have different contents then
the same directories in a regular terminal. But don't fret, you can ask acme to
run a command using the namespace of the shell that invoked it with the Local
command. So middle clicking Local tile will tile your rio windows just fine (to
"middle click" two words you need to middle click and drag to select the text).
Another way to do this, if you plan on using tile a lot in acme, is to write a
wrapper script for it in /acme/bin/Tile:
!/bin/rc
Tile - wrapper for tile
tile $*
As you can see, this is just an ordinary shell script. The only difference is
that while acme binds /acme/bin to /bin, other programs don't, and hence files
in /acme/bin are specific to this programs namespace. When we execute tile here
it will have a regular shell namespace, and thus work as expected. Note that
our acme wrapper has a capital T, to avoid a naming conflict with our rio
tiling script. There are other simple shell scripts you may want to add to /
acme/bin. In our introductory section on acme, we listed several examples of
how you can do basic text editing operations using external tools. It is easy
to make acme commands out of these examples, by adding shell scripts for them
in /acme/bin. For example, we could write the following t+ and t- scripts, to
indent and unindent text:
!/bin/rc
t+ - indent text
sed 's/^/ /'
!/bin/rc
t- - unindent text
sed 's/^ //'
We can now indent lines in acme by highlighting (left click and drag) the text
and pipe it to our new script by middle clicking |t+. Naturally we can just as
easily write commands for commenting and uncommenting text, for adding and
removing line numbers to a file, for upper and lower casing text, for
obfuscating text with rot13, and so on. But the real fun stars when you begin
to script acme itself! Like rio, acme can be fully controlled by writing plain
text to a set of control files. Lets look at a couple of quick examples:
Coffee - Chill ASCII Animations
Suppose we have created a series of ASCII coffee mugs in a directory, our first
artwork, $home/lib/animation/coffee/1, may look something like this:
(
) (
( ) (
,---------.__
| | |
| | |
| | |
| |=='
| |
`---------'
COFFEE BREAK
The other files I will leave to your imagination, but the point is that when
they are displayed in rapid order, an ASCII animation of a steaming coffee mug
will be the result. In a rio terminal, we could write such an animation script
like this:
!/bin/rc
coffee - print ASCII animation of steaming coffee mug
usage: coffee
play $home/music/samples/coffee.mp3 >[2]/dev/null &
while()
for(i in $home/lib/animation/coffee/*)
> /dev/text && cat $i && sleep 1
But this will not work in an acme win terminal, since we don't have /dev/text
in our namespace. To clear the text in an acme window we need to write the
command Edit ,d (that is :%d for all you vi users out there), select this
command, then click it with our middle mouse button. Can we do this
programmatically? Sure:
!/bin/rc
echo -n Edit ,d > /dev/acme/body
echo -n /Edit ,d/ > /dev/acme/addr
cat /dev/acme/addr | awk '{ print "MX", $1, $2 }' > /dev/acme/event
This essentially follows the three steps mentioned above. The last line is the
most cryptic. What we are doing here is reading the address (the marked text),
which returns information like the beginning and end positions, which we then
feed to awk. We also append the "MX" command to event, which tells acme that a
middle mouse button was "clicked" on this region of text.
Lets call this script /acme/bin/clear, and lets add a script in $home/bin/rc/
clear that clears a rio terminal:
!/bin/rc
clear - clear up a rio terminal
usage: clear (see also /acme/bin/clear)
/dev/text
We can now adjust our coffee animation script so that it works in both the rio
terminal and in acme's win terminal:
while()
for(i in $home/lib/animation/coffee/*)
clear && cat $i && sleep 1
Slides - Acme Presentations
Here is another simple example. Suppose we have a directory of files called 1,
2, 3... each providing a slide in a textual slide show. We could open the first
slide by right clicking 1. Then manually editing the filename to 2, type Get
and middle click it. Annoyingly we would need to click Get twice, since acme
will warn us that loading this file will change the contents of our window.
Lets automates this:
!/bin/rc
Slide[-+] - go back and forwards in a slide show
usage: Slide[-+]
#
bugs: slides must be named 1, 2, 3...
to "install" the script copy it to /acme/bin/Slide^('' - +)
(that is to /acme/bin/Slide{,-,+} in UNIX speak)
switch($0){
case *Slide
ls `{pwd}
exit
case *Slide+
page=`{echo `{basename $%} + 1 | hoc}
if(! test -f $page) exit
case *Slide-
page=`{echo `{basename $%} - 1 | hoc}
if(! test -f $page) exit
case *
echo Error: bogus program name!
exit wrongname
}
echo 'name '`{pwd}^/$page'' > /mnt/acme/$winid/ctl
echo clean > /mnt/acme/$winid/ctl
echo get > /mnt/acme/$winid/ctl
To install this script copy it to /acme/bin/Slide and make it executable, then
copy this script to Slide+ and Slide- in the same location. If we now open up
one of our iterative slides in acme, we can middle click Slide+ to advance to
the next slide, Slide- to go back to the previous one, or Slide to list our
slides. We can click Slide+ or Slide- repeatedly, the slide show will stop once
we reach the end, or the beginning, respectively.
Our script contains a couple of special variables, $0 refer to the name of the
program that is running. The behavior of our program will change depending on
what it's called, if it's called Slide+ it will advance the slide, if it's
called Slide- it will retreat the slide, and so on. $% is a variable particular
to acme, it refers to the filename in the tag of the current acme window. The
last three lines are simple enough, change the filename, tell acme not to
bother us about contents changing, and finally load the new file.
PS: This code is not exactly original, it is heavily inspired by the Slide
scripts from Russ Cox.
Chat - Simple Peer to Peer Chatting
[dare_calls]
Long before the days of modern chat protocols, such as IRC, the ancient UNIX
systems came with a simple peer-to-peer chat program called write. This program
established a simple connection between two users, and just wrote whatever the
users had written verbatim to a common text window. The text would be garbled
if both users wrote simultaneously, so it was customary for the user who had
initiated the conversation to write first, and end his input with (o), for
"over". Then the other user would reply, and end his input with (o). And
finally, when the conversation had run its course, a user would signal that he
ended the conversation with (oo), for "over and out".
Surprisingly enough, you will actually find this 50 year old program on most
modern UNIX boxes today, even though nobody uses it. Plan 9 however is a modern
operating system for the 90's, and thus do not include this archaic program.
But if you are feeling nostalgic, it's trivial to implement it:
; touch /usr/chat && chmod 666 /usr/chat
; tail -f /usr/chat &
; while(){ read >> /usr/chat }
An arbitrary number of users can write the same commands, and join the chat,
remote Plan 9 users too, they just need to import the chat machines file
system, and they are good to go. Whatever people write to the file will be
printed verbatim to all that are viewing it. But this solution is awkward. For
one, the UNIX write command notified the user you wanted to talk to (in the
increasingly unlikely event that he worked on a text console), ours doesn't.
And there are some other rough edges besides. Surely we can write a nicer acme
client for this? Let's start off by implementing a simple notification system;
we can do so in various ways, but here is a quick suggestion:
; touch $home/lib/notify
; chmod 666 $home/lib/notify # allow everyone read/write access
; B $home/lib/profile
...
notify daemon (see statusmsg(8))
while(sleep 5){
if(test -s $home/lib/notify)
@{cat $home/lib/notify; sleep 5} | aux/statusmsg
> $home/lib/notify
}&
usage: notify user message...
fn notify{
recv=$1 && shift
if(test -w /usr/$recv/lib/notify)
echo $* >> /usr/$recv/lib/notify
}
miscellaneous oldschool commands
fn write{
echo 'Use Chat in acme you Neanderthal!'
}
fn wall{
for(recv in `{who}) notify $recv $*
}
fn mesg{
if(~ $1 y) chmod 666 $home/lib/notify
if(~ $1 n) chmod 644 $home/lib/notify
}
fn finger{
whois $1
for(file in /usr/$1/lib/^(plan project))
if(test -f $file) cat $file
}
clean up old chat logs
rm -f $home/lib/chat
...
; reboot
With a notification mechanism in place, we can go ahead and write our acme Chat
client (we use the unintuitive variable $recv for our message receivers since
$user is already taken, it refers to your user). We'll implement it as two
commands, Chat for connecting to a chat session, and Reply for taking whatever
we have written in the tag line, and add it to the chat log.
!/bin/rc
Chat - open a new chat window
usage: Chat user...
are we host or client?
rfork ne
if(~ $#* 0) exit
if(~ $#* 1 && test -f /usr/$1/lib/chat) host=$1
if not host=$user
log=/usr/$host/lib/chat
tag=' ['$host'] Reply '
if(~ $host $user){
touch $log && chmod 666 $log
for(recv in $*) notify $recv $user wants to Chat!
}
set up chat window
id=`{awk '{ print $1 }' /dev/new/ctl}
for(cmd in nomenu cleartag scratch) echo $cmd > /mnt/acme/$id/ctl
echo -n $"tag > /mnt/acme/$id/tag
tail -f $log > /mnt/acme/$id/body >[2]/dev/null
!/bin/rc
Reply - write tag comments to a chat log
usage: Reply comment...
rfork ne
tag=`{sed 's/.*(\[.+\] Reply).*/\1/' /mnt/acme/$winid/tag}
host=`{echo $tag | sed 's/.*\[(.+)\].*/\1/'}
reply=`{sed 's/.*Reply //' /mnt/acme/$winid/tag}
echo $user: $reply >> /usr/$host/lib/chat
echo cleartag > /mnt/acme/$winid/ctl
echo ' '$"tag' ' > /mnt/acme/$winid/tag
The Chat program first determines if we are the chat host or not. (it's the
host that maintains the log and invites the guests) Next, the program spawns a
new acme window by reading /dev/new/ctl, the first argument returned when
reading this file, is the ID number of our newly spawned window. Then we write
a few commands to the control file of the new window, specifying that it should
have an empty tag line, and that acme shouldn't warn us if the content of this
window changes. Finally we add the hosts name and the command Reply to the tag
line, and start listening for changes to the chat log, which will be printed to
the body of our new window.
To write something in the chat window, just add your comment after the Reply
command in the blue tag line, and middle click Reply when you're done. The
comment will be added to the chat log, prefixed with your user name, and the
tag line will be reset. Our command contains some odd regex, a (.+) sed
argument would be \(..*\) in UNIX. Plan 9 utilities all use the egrep like
regexp(6) library for regular expressions. Another detail: ' '$"tag' ' is ugly,
but necessary to preserve whitespace correctly.
Naturally, our Chat program is amazingly primitive; it's method of choosing a
host and cleaning up old chat logs is sloppy and it lacks many common features.
Compared to UNIX write however, it's actually quite advanced; We can chat with
an arbitrary amount of people over the secure network protocol 9P, the users
are identified and can write simultaneously without garbling the output, and we
even have a GUI notification mechanism. It's not a bad starting point, but feel
free to expand the code to suit your own needs :)
Play - An Acme Music Player
In the audio section below, we list some simple shell functions for pausing,
resuming and skipping songs we are playing. We can easily write some of these
as shell scripts in $home/bin/rc, and thus also use them in acme (we could
place them in /acme/bin, if we only want to use them from acme):
!/bin/rc
skip - skip a song that is playing
kill pcmconv | rc
!/bin/rc
pause - pause a song that is playing
stop pcmconv | rc
!/bin/rc
resume - resume a paused song
start pcmconv | rc
!/bin/rc
vol - adjust audio volume
usage: vol n
echo master $1 $1 > /dev/volume
But these functions have two limitations, first they do not show you a visual
playlist, neither do they allow you to move freely back and forth in the
playlist, you can only skip the current song and play the next one on the list.
If you think about it though, que (a script mentioned later) has the needed
functionality for iterating over a playlist, and acme, being a text editor
after all, has the needed functionality to visualize and edit such a list. It
turns out that wrapping these things into a cohesive GUI is very easy. Our acme
Play command looks like this:
!/bin/rc
Play - play a list of audio files
usage: Play
bugs: must run command in a window with audio filenames
echo cleartag > /mnt/acme/$winid/ctl
echo ' Play Quit Repeat ' > /mnt/acme/$winid/tag
while(){
if(! test -d /mnt/acme/$winid) exit
if(! grep -s '<--' /mnt/acme/$winid/body &&
grep -s Norepeat /mnt/acme/$winid/tag) exit
song=`{que $%}
echo clean > /mnt/acme/$winid/ctl
echo get > /mnt/acme/$winid/ctl
play $song > /dev/null >[2=1]
}
To use this program, we must first write a playlist of audio files. We can
easily generate one, by running something like: du -a $home/music/creedence |
awk '/\.mp3/ { print $2 }' | sort > $home/lib/playlist/creedence Lets assume we
have opened a playlist in acme that looks like this:
/usr/glenda/music/creedence/01_pagan_baby.mp3
/usr/glenda/music/creedence/02_sailors_lament.mp3
/usr/glenda/music/creedence/03_chameleon.mp3
...
If we type Play now in the tag line and middle click it, it will add the
commands Quit and Repeat to our tag line, and start playing 01_pagan_baby.mp3,
and then update our playlist, so that it looks like this:
/usr/glenda/music/creedence/01_pagan_baby.mp3
/usr/glenda/music/creedence/02_sailors_lament.mp3<--
/usr/glenda/music/creedence/03_chameleon.mp3
...
When 01_pagan_baby.mp3 is finished playing, 02_sailors_lament.mp3 will start
playing, and the "<--" marker will move to 03_chameleon.mp3, the next song to
be played, and so on. This will continue indefinitely, repeating the playlist
over and over again. If we middle click Repeat however, it will change to
Norepeat and the playlist will only play once, then stop. (this relies on the
convention that que removes the "<--" marker once the queue is finished)
Finally, just Del'ing this window does kind of work, but the last song will
continue to play until it is finished. To gracefully quit both this window and
the audio playing, middle click Quit. Here are our support scripts:
!/bin/rc
Quit - quit Play
stop pcmconv | rc
echo clean > /mnt/acme/$winid/ctl
echo del > /mnt/acme/$winid/ctl
start pcmconv | rc
kill pcmconv | rc
!/bin/rc
Repeat - toggle norepeat for Play
echo cleartag > /mnt/acme/$winid/ctl
echo ' Play Quit Norepeat ' > /mnt/acme/$winid/tag
!/bin/rc
Norepeat - toggle repeat for Play
echo cleartag > /mnt/acme/$winid/ctl
echo ' Play Quit Repeat ' > /mnt/acme/$winid/tag
What is important to note here, is that the playlist is just a plain text file.
So we can freely add or remove lines here as we see fit, we can also freely
move the "<--" arrow to whatever line we want. After we have middle clicked Put
to save our changes, the next song to be played will be whatever line our arrow
is at. So how do we shuffle our playlist? If we use acme in Linux with
Plan9Port, we can just mark the playlist and middle click |shuf (or Edit ,|shuf
if it's a very long playlist). But Plan 9 has no shuf command! No matter, we'll
just make one. This crude solution should suffice for our needs:
!/bin/rc
shuf - shuffle input lines
usage: shuf < input > output
ifs='
'
fn sigexit{ rm -f /tmp/shuf-$pid }
for(line in `{cat /fd/0}){
for(i in 1 2 3) awk '{ printf("%d", substr($2,19)) }' /dev/time
echo @@@$line
} >> /tmp/shuf-$pid
sort -n /tmp/shuf-$pid | sed 's/^[0-9]+@@@//' > /fd/1
exit # force clean up
In this script we are using the last nanosecond of the computer clock to
generate some fairly random numbers. /fd/0, /fd/1 and /fd/2 are equivalent to /
dev/stdin, /dev/stdout and /dev/stderr in UNIX (and ifs equivalent to IFS
naturally - Plan 9 is even more lower case oriented then UNIX). The last exit
here is ugly, but necessary make the sigexit trap to work (Plan 9 pays homage
to unreliable UNIX signals).
We have only provided a handful of crude scripts in this section, but hopefully
they illustrate how easy it is to expand acme's capabilities using only a
handful of tiny shell scripts. Who needs a bloated graphical toolkit anyway,
when you've got acme!
Web Scripting
As mentioned elsewhere, Plan 9 networks are controlled through plain files.
This implementation is unusual, and you should read /sys/doc/net/net.ps to
familiarize yourself with the concept. As with the notion that a desktop is
controlled by writing text strings to files, this idea may seem bizarre or even
amusing at first. But any smirk you may have quickly fades as the rio scripting
section describes how to develop advanced desktop features with simple shell
scripts (try enabling virtual workspaces in Windows 7 with CMD!). I think the
same will be true for web scripting. Here is a fully-fledged telnet
implementation just to wet your appetite:
!/bin/rc
clonefile=/net/tcp/clone
<[4] $clonefile {
netdir=`{basename -d $clonefile} ^ / ^ `{cat /fd/4}
echo connect $1|$2 >$netdir/ctl || exit 'cannot connect'
cat $netdir/data &
cat >$netdir/data
}
9front Web Scripts
9front ships with the IRC client ircrc, the pastebin command webpaste, and the
hget and hpost commands. All of these programs are shell scripts. hget and
hpost are somewhat like wget and curl in UNIX, but they are only a hundred, and
two hundred, line shell scripts, respectively (in contrast wget and curl are
300,000 lines of C each!). We will not print their source code here, but they
are worth studying if you plan on writing web scripts in Plan 9 yourself. As
for webpaste it is just a few lines long, and it is a good demonstration of how
to transmit data to a web service (it depends on hpost):
!/bin/rc
if(~ $#* 0)
file=/fd/0
if not
file=$1
hpost -u http://okturing.com -p / a_body@file submit:submit fake:fake a_func:add_post url: |
grep -e '\/body\"' |
sed 1q | sed 's/^.*href=\"//g; s/body\".*$/body/g'
PS: Classic Plan 9 does not include the above mentioned scripts.
ircrc is also about two hundred lines of code, and is well worth studying. But
here is a stripped down version to illustrate what is possible in Plan 9. It is
a very primitive IRC client that only supports a few IRC commands and hardcodes
your nick and channel, but it is a working IRC client nonetheless. And at under
70 lines of shell that isn't bad at all:
!/bin/rc
rfork ne
server=irc.oftc.net
port=6667
realname=myrealname
target='#cat-v'
netdir=()
nick=mynick
fn sighup {
exit 'hang up'
}
fn sigint sigterm {
if (! ~ $#netdir 0)
echo QUIT : Leaving... > $netdir/data
}
fn mshift {
shift
echo $*
}
fn etime {
date | awk '{print $4}' | awk -F ':' '{print "[" $1 ":" $2 "]"}'
}
fn work {
echo USER $user foo bar :$realname > $netdir/data
echo NICK $nick > $netdir/data
echo PRIVMSG 'nickserv :'identify $"pass > $netdir/data
echo JOIN $target > $netdir/data
while (cmd=`{read}) {
s=$status
if(~ $s *eof) {
echo QUIT : Leaving... > $netdir/data
exit
}
msg=()
out=()
switch ($cmd(1)) {
case /j
if (~ $#cmd 2) {
target=$cmd(2)
msg = (JOIN `{mshift $cmd})
}
case /q
msg = `{mshift $cmd}
case /x
echo QUIT : Leaving... > $netdir/data
exit
case /*
echo unknown command
case *
msg = 'PRIVMSG '^$target^' :'^$"cmd
out = '('^$target^') ⇐ '^$"cmd
}
echo $msg > $netdir/data
echo `{etime}^' '^$out
}
}
userpass=`{auth/userpasswd 'server='^$server^' service=irc user='^$nick >[2]/dev/null}
if(~ $#userpass 2) {
nick=$userpass(1)
pass=$userpass(2)
}
p='/n/ircrc'
bind '#|' $p
echo connecting to tcp!$server!$port...
aux/trampoline tcp!$server!$port <>$p/data1 >[1=0] &
netdir=$p
work
Development
[for_resear]
As this article is about using Plan 9 as a desktop, we will only mention
development in passing. The programming language used throughout the system is
C, or more specifically a Plan 9 dialect of C. The system also has its own set
of compilers and linkers, one set for each supported architecture. To
demonstrate what programming may look like in Plan 9, we can build on the
classic Fahrenheit-Celsius conversion program of The C Programming Language
(page 15). This is a somewhat useful program since units(1) will not handle
that particular conversion. The classic version will actually build just fine
in Plan 9 with some tiny modifications,* but just for the fun of it we can add
a -c flag for Celsius to Fahrenheit conversions, and a -d degree flag for
converting a specific temperature rather then printing a table of values:
; ed fahcel.c
?fahcel.c
a # ed: append text
include <u.h>
include <libc.h>
define LOWER -50
define UPPER 150
define STEP 10
define F2C(deg) (5.0/9.0)*(deg-32)
define C2F(deg) (deg*(9.0/5.0))+32
void usage(void);
/* Fahrenheit-Celsius converter */
void
main(int argc, char* argv[])
{
int cflag = 0;
int dflag = 0;
double deg;
/* parse arguments */
ARGBEGIN{
case 'c':
/* Celsius to Fahrenheit */
cflag = 1;
break;
case 'd':
/* specify a temperature to convert */
dflag = 1;
deg = atof(EARGF(usage()))
break;
default:
usage();
}ARGEND;
if (argc)
usage(); /* no more args! */
/* print temperature conversion */
if (dflag) {
print("%4s", cflag ? "Fahr" : "Cel ");
print("%6.1f\n", cflag ? C2F(deg) : F2C(deg));
} else {
print("%10s\n", cflag ? "Fahr Cel" : "Cel Fahr");
for (deg = LOWER; deg <= UPPER; deg = deg + STEP)
print("%3.0f %6.1f\n", deg, cflag ? C2F(deg) : F2C(deg));
}
exits(nil);
}
/* exit with a usage statement */
void
usage(void)
{
sysfatal("usage: fahcel [-c] [-d deg]");
}
. # ed: end text input
w # ed: write file
938
q # ed: quit
; 6c fahcel.c
; 6l -o fahcel fahcel.6
; fahcel -c -d -12.3
Fahr 9.9
; fahcel
Cel Fahr
-50 -45.6
-40 -40.0
-30 -34.4
...
Don't worry about the ed stuff if you're not used to this editor, there are
alternative text editors in Plan 9 that will also be unfamiliar to you. Plan 9
users will often open a file with the B command, which will open the file in
whatever text editor happens to be open, or it will launch the default editor
if none is running (usually sam, add editor=acme to $home/lib/plumbing, if you
prefer acme instead).
This program might look a little overwhelming to a non-programmer, but for
seasoned developers it should be straight forward. Although the C code looks
much like a UNIX equivalent, a keen observer will notice many startling
differences. Most Plan 9 programs only have two included headers, the
architecture dependent code u.h and the standard library libc.h. Notice also
that it's perfectly legal for a main to return void, and that exits, not exit,
returns a string. atof, what?!? Thats unsafe! Not in Plan 9.* The ARGBEGIN/
ARGEND* macros and sysfatal function in Plan 9, are also elegant ways of
handling command line arguments and fatal errors, in my opinion. Since all
programs in Plan 9, with more then a couple of flags, use these macros, option
handling is consistent. There are no long options, but single letter flags can
be typed in any combination. And arguments to flags can be typed in any
combination. Eg. -c -d -12.3, -cd -12.3, -c -d-12.3, -cd-12.3 are all legal. If
you have to type an uninterpreted flag, for example, if you need to delete a
file that is literally called -rf, you can precede it with a double dash: rm --
-rf If only UNIX had been this consistent...
There are other differences too. For one we see that a program in the current
directory can be executed just by giving its name, in UNIX this isn't usually
tolerated, or at least frowned upon. To force the UNIX behavior, just set path=
/bin in $home/lib/profile.* Another difference is that compiler and linker are
two separate programs, and that each architecture has their own set. This makes
it very easy to cross-compile programs. For instance, in the above example a
64-bit PC architecture is assumed, but on a 64-bit PC you can easily compile
32-bit programs using 8c and 8l, or you can compile ARM programs using 5c and
5l (see 2c(1)). Of course you cannot run ARM programs on PC hardware, but a
Raspberry Pi running Plan 9 can easily compile its software on a PC running
Plan 9 (or vice versa for that matter). In fact it's easy-peasy to cross
compile a 32-bit Plan 9 system into 64-bits (see section 5.2.2.1 in the 9front
fqa).
Probably, the best place to start if you want to develop in Plan 9, is to read
the article C Programming in Plan 9. The specific details of the Plan 9 C
dialect is discussed in /sys/doc/comp.ps. Other important papers in this
directory are the acid debugger paper acidpaper.ps and the mk paper mkfiles.ps
(equivalent to make in UNIX). The other papers here will also give you some
useful hints, but be aware that some of them are quite dated. Lastly, manpages
and source code is very readable in Plan 9, so use that for what it's worth!
The src command will let you quickly look up source code for any given command,
eg. src echo. Another resource that I highly recommend is Introduction to
Operating Systems Abstractions Using Plan 9 from Bell Labs, by Francisco J.
Ballesteros. You can download the PDF for free from the internet, and despite
its tedious name, it is a marvelous programming introduction to Plan 9.
Naturally many classic UNIX resources are also useful for Plan 9, even though
many details aren't directly applicable, such as the aforementioned The C
Programming Language by Kernighan and Pike, The UNIX Programming Environment by
the same, and The AWK Programming Language by Aho, Kernighan and Weinberger.
Beyond shell and awk programming, there are also some support for external
programming languages, such as POSIX C and sh (see /sys/doc/ape.ps), Perl,
Python, Go, Scheme, Lua, and Limbo if you install Inferno (see appendix L in
9fronts fqa). Generally though C and shell programming are by far the best
supported languages. The Perl port is very old for instance, and Python was
recently dropped from 9front (you can still get it if you want - see
instructions below).
Version Control
All the Plan 9 file systems (cwfs, hjfs or gefs in 9front, or fossil or kfs in
classic Plan 9) have built-in support for snapshots. And like all file systems
with snapshot capabilities, only the difference between the versions are saved,
so a snapshot of a 1 Gb file with 10 Kb of difference, will only consume 10 Kb
of disk space. Snapshots are usually taken at regular intervals automatically,
but you can take one manually if you want, e.g. echo dump >>/srv/hjfs.cmd (use
/srv/cwfs.cmd if you are using the cwfs file system). To read snapshots run 9fs
dump, the files will be located in /n/dump. For example if you are looking for
the snapshot of /usr/glenda/prj/code.c taken 23 February 2020, it will be
located in /n/dump/2020/0223/usr/glenda/prj/code.c. The yesterday command is a
quick way to print the path of the most recent snapshot. history will print all
available snapshots where the file content differs.
GEFS works a little differently, to mount the snapshots you can run mount /srv/
gefs /n/dump dump. Normally, you are not allowed to change file ownership and
do other admin tasks that can compromise security. A user in the adm group can
circumvent that restriction by mounting the filesystem permissively in a
placeholder location, and do the needed admin tasks there. Eg. mount /srv/gefs
/n/u %main; chgrp -u bob /n/u/usr/tom By default GEFS takes snapshots very
quickly, but you can adjust this behavior and manually label your own
snapshots. You can also regulate how often automatic checkpoints are taken per
snapshot, and thus control how often certain parts of your filesystem is backed
up. A common use case is to make a snapshot of main called other, and turn off
automatic checkpoints in that snapshot. With such a setup, you can disable
snapshots in $home/tmp like so: mount /srv/gefs /n/o %other; mkdir -p /n/o/
$home/tmp To illustrate some GEFS operations:
; con -C /srv/gefs.cmd
gefs# snap -l # list snapshots
gefs# set retain @d # only take one snapshot daily
gefs# snap main keep # make an read-only snapshot called "keep"
gefs# snap -m main fork # make a mutable (changeable) snapshot called "fork"
gefs# set fork retain '' # don't take automatic checkpoints of this snapshot
gefs# snap -d fork # delete the "fork" snapshot
gefs# Ctrl - \
>> q # quit con
The Bell Labs developers used the built in snapshot feature of Plan 9 as their
version control system, and for personal use this works great. But if you are
collaborating online with other programmers, or if you are working on non-Plan
9 software, you probably want to use a more traditional version control
software. Previously the 9front developers used mercurial to maintain their
project, but recently a switch was made to git, using the Plan 9 port called
git9 (it is still possible to get mercurial if you really need it):
Installing python and hg (mercurial):
; cd /tmp
; git/clone gits://git.9front.org/plan9front/pyhg
; cd pyhg
; install.rc
Files and Namespaces
The big difference between UNIX and Plan 9, which is especially important for
developers to note, is that while "everything" is a file in UNIX, everything is
a file in Plan 9! There are no sockets or ioctl for instance, all networks and
devices are controlled through plain files. It is hard to emphasize just how
much simpler this makes programming, but some illustrative examples can be
found in the Automation sections above.
In order to make everything in this dynamic and complex world of ours work as
files, Plan 9 uses some conventions and mechanics that are unfamiliar to UNIX
users. For example, devices often need control interfaces as well as input/
output interfaces, so Plan 9 often implements a device as a directory with
multiple files. For example audio input/output is handled through /dev/audio,
but the control interface is /dev/audioctl, and hardware statistics are
available through /dev/audiostat (in 9front that is). Another example is the /
bin directory, which unlike UNIX contains all available programs on the system.
However, what that means may differ from program to program - and notably, from
process to process. Files in /bin can also be directories that group related
programs together. All games are in /bin/games for instance, so launching /bin/
games/sudoku requires you to type games/sudoku.
There are no links in Plan 9, instead files and directories can be bound to
different locations using the bind command. Lets again consider /bin: Programs
are actually sprinkled in different places throughout the system, 32-bit PC
binaries are in /386/bin, 64-bit PC binaries for acme are in /acme/bin/amd64.
Shell scripts are in /rc/bin and personal shell scripts are in $home/bin/rc,
and so on. When the system boots, the relevant program directories are all
bound to /bin. If the system is 32-bit PC /386/bin is bound to /bin, if it's a
64-bit Sparc system /sparc64/bin is bound instead, and so on. Programs are free
to build on this. acme will add /acme/bin for instance, sam will not. The
important lesson here is that the correct filestructure is assembled during
boot. To see the full details of how your filestructure is assembled, use the
ns command. This namespace can be manipulated freely, and it only effects the
current process (and any child processes executed afterwards).
For example, in the previous version control section, we mentioned how you can
look up old versions of some file. But this might be tedious if we need to
check out many files of a given date, in such a case it would be simpler to
manipulate our namespace. Let say we want to check out all of our project
files, as they were on 23 February 2020, the following commands should do the
trick:
; 9fs dump
; bind /n/dump/2020/0223/usr/glenda/prj $home/prj
for gefs
; mount /srv/gefs /n/dump dump
; bind /n/dump/main@day.2020.02.23_12:00:00/usr/glenda/prj $home/prj
Now all of the files in $home/prj in this window refer to our old copies of
2020. To go back to the current version of this directory, just run unmount
$home/prj (that's unmount, not umount). The bind command can be used much like
ln in UNIX, to create shortcuts from one point to another in the file system.
But it is much more powerful. For one it doesn't care if the files are in the
same file partition, or even on the same physical machine. For another you
don't need to replace directories, you can merge them. That is what Plan 9 does
with /bin, many directories are bound together in this location. Various flags
to bind let you specify if the directory should be prepended or appended, and
whether or not to allow file creation in such a union.
Another example of such namespace manipulation is the rcpu (cpu in classic Plan
9) command, which binds a remote CPU servers processor to the current process,
while the local files and devices, such as the keyboard, are kept unmodified.
The window still looks and behaves like a normal Plan 9 terminal, but it's now
using the remote machines processor. This is handy if the remote machine is
fast, while the local machine is slow or over taxed. It is also useful if you
are testing software for a different architecture, such as running ARM programs
from a PC or vice versa. Other remote resources can be imported as well, such
as an external audio or ethernet device (and thus create a very simple MPD/VPN
service). Again, only the process in question is manipulated, other running
processes are unaffected. Namespaces lies at the very heart of Plan 9's
capabilities, but it's hard for UNIX users to grasp the concept. If it helps,
think of each Plan 9 process running inside its own mini-jail. The difference
though is that namespaces in Plan 9 were not primarily devised as a way to
isolate resources, but a way to distribute them.
If you really want a jail though, it's simple enough to implement one:
; rcpu -u loser # unprivileged user
; plumber # isolated inter-proc messaging
; auth/factotum -n # isolated auth services
; rio # isolated desktop
; mkdir fake
; bind fake $home # sandbox home directory
; rfork n # isolated namespace
; rfork e # isolated environment variables
; rfork s # isolated signals
; rfork f # isolated file descriptor table
; rfork m # disallow mounts
You can mix and match these commands to give your jail more or less powers, and
you can manipulate files in /dev and /net to grant or deny various devices and
networks. Read fork(2) for more details.
Sidenote: You'll notice that many of the scripts in this article start with
rfork e. This ensures that the variables used in the script are not
inadvertently blended with the parent. On rare occasions you may want that. The
max script above for example shares its environment variables with the parent.
It sets the windowsize variable to the current window size, if it isn't already
set. This value is then later used by another invocation of max to restore a
window to its former dimensions. (thus "unmaximizing" it) Generally though, it
is hazardous to use global variables, so start your scripts with rfork e. It is
prudent to use rfork ne if you are working with directories that may change,
such as /mnt or /tmp, and in rare occasions you may want to run plain rfork (it
defaults to rfork nes) to isolate your script even further. It ensures a stoic
indifference if your parent commits seppuku.
The Web
[free_flow]
If you have a wired connection to the internet (you do if your using a virtual
machine), you should already be connected. If not run the command ip/ipconfig.
If your having trouble, you can run the netaudit script, and see if the network
status looks like it's supposed to (classic Plan 9 does not have this script).
Wireless Network
Wireless networking is only supported in 9front. During startup you may see a
line similar to #l1: '/lib/firmware/iwn-6005' does not exist (you can check
startup messages later with cat /dev/kmesg). This tells you that firmware for
the wireless device #l1 is missing.
9front uses firmware from OpenBSD, so download the correct package for your
device from firmware.openbsd.org, and unpack it in /lib (if you don't have
wired internet access, put this file on a FAT32 formatted USB stick, using
Ubuntu or Windows or whatever and transfer it that way), reboot, and then you
can connect to a wireless network. To illustrate:
; cd /lib
; hget http://firmware.openbsd.org/firmware/7.7/iwn-firmware-5.11p1.tgz | tar xz
; reboot
; bind -a '#l1' /net # you might want to add this to $home/lib/profile
; aux/wpa -s mynetwork -p /net/ether1
!Adding key: proto=wpapsk essid=mynetwork
password: ******
!
; ip/ipconfig ether /net/ether1
You can easily automate the last two steps with a short script, and thus
connect to a wireless network with wifi mynetwork:
fn wifi{
aux/wpa -s $1 -p /net/ether1
ip/ipconfig ether /net/ether1
}
fn wifiscan{
# scan for available networks
cat /net/ether1/ifstats
}
Normally, 9front uses the ethernet as its default network card, but you can
overwrite this. Adding ether0=type=iwl in plan9.ini, will tell the system to
use the wireless network card as /net/ether0. (see plan9.ini(8) for more
details)
PS: Make sure the firmware package has the file you need (iwn-6005 in the above
example), it might not be the latest one. You also need to recompile the kernel
if you want the network card enabled at boot time.
Browsing The Web
[browsers]
The preferred web browser for 9front is mothra, but the classic Plan 9 browser,
abaco, is also available. Both of these web browsers have only basic support
for HTML, they do not support any CSS, let alone JavaScript. Also, you must
supply a full URL with a protocol prefix, eg. https://www.wikipedia.org, not
just wikipedia.org. (it might help to have a list of JS-unfriendly web sites
handy)
To open a local HTML file in mothra, write file:///path/to/file. To download
content from a website, right-click and choose moth mode. The mouse cursor will
change to a moth, you can now click on any link or image to download it. Choose
moth mode again, to return to the default mode, where clicking on a link will
follow it instead of downloading it (abaco cannot open local files or download
content).
As for acme, you cannot use it to browse the web interactively, but you can do
a basic text dump of a webpage, by middle clicking something like wurl2txt
http://9front.org.
Recently, NetSurf has been ported to Plan 9 by the 9front developers. The
browser is slow and glitchy with a ton of bugs, and thus provides a fairly
convincing web 2.0 experience. It is still a very simple browser though, so
don't expect to do your online shopping in Plan 9 anytime soon (you can do
youtube - but not in a browser).
Install the Plan 9 port of NetSurf from github:
; cd $home/src
; git/clone https://github.com/netsurf-plan9/nsport
; cd nsport
; fetch clone http
; mk
; mk install
; netsurf # browse! (make sure webfs is running)
[netsurf]
Downloading
In addition to downloading files interactively with a browser, you can get
files with hget (it works much like wget in UNIX). There is also ip/torrent for
downloading torrents.
Hints on setting up an email server can be found in section 7.7 of the 9front
fqa. And see section 8.4.1.1 for tips on interfacing the Plan 9 mail server
with GMail. Once you have configured this to your suit your needs, Plan 9
provides a few alternative email clients. Probably the most useful one will be
acme's mail client Mail, but you can check out faces and nedmail too if you
want.
Chatting
To join the fairly active #cat-v channel, on irc.oftc.net, where 9front
developers hang out, run this command ircrc -n mynick -t '#cat-v'. A few
alternative 3rd party IRC clients are also available, and an xmpp (jabber, eg.
Google Talk) client. You can check out the latter projects web site if you are
interested.
Install and use the ircs persistent IRC client:
; cd /tmp
; 9fs 9front
; tar xzf /n/extra/src/ircs.tgz
; cd ircs
; mk install
; ircs -p dansimon irc.oftc.net # start IRC client
; ircx -t '#cat-v' # start IRC user interface
An IRC log is available in /tmp/ircs/log. The first time you run this, factotum
will ask you for your password (the -p in ircs means 'use a password'). You can
add this password permanently the the secstore file factotum, for a fully
automatic authentication.
Running a Web Server
Plan 9 is quite capable of serving web pages, just as long as you keep things
simple. To quickly set up a local web page, do the following:
Write some static html file(s), $home/www/mysite/index.html for instance. And
check that it works (eg. mothra file://$home/www/mysite/index.html). Now we can
configure the rc-httpd web server, by adding the following to /bin/rc-httpd/
select-handler:
if(~ $SERVER_NAME mysite){
PATH_INFO=$location
FS_ROOT=/usr/myname/www/$SERVER_NAME
exec static-or-index
}
Start the web server by running aux/listen1 tcp!mysite!80 rc-httpd/rc-httpd
(make sure you middle click the terminal window and select "scroll"). You
should now be able to connect to your web server with mothra http://mysite!
PS: Substitute mysite for whatever hostname or ip address is appropriate for
your machine.
Multimedia
[blackn_whi]
9front has quite decent multimedia support as we shall see, but classic Plan 9
systems are very limited in this respect. They only support SoundBlaster cards
for instance, and MP3 file formats (oddly enough you will find the MP3 decoder
and encoder under /bin/games in classic Plan 9). To enable audio in a qemu
virtual machine (for both 9legacy and 9front), run qemu with the -device sb16
flag, and add this line to plan9.ini: audio0=type=sb16 port=0x220 irq=5 dma=5.
After this, you still need to bind the audio device in 9legacy, like so: bind
-a '#A' /dev. You may want to add this command to $home/lib/profile. You don't
need to mess with your profile in 9front however, and audio should just work
out of the box on real hardware.
Audio
Adjusting the volume, to say 80%, can be done like this: echo 80 > /dev/volume,
or more precisely: echo master 80 80 > /dev/volume (80% for left and right
speakers). But switching between headphones and speakers can be a bit tricky.
If you're are lucky the hardware will just take care of it, but if you aren't
you have to manually redirect audio pins. On one of my machines the command
echo pin 21 > /dev/audioctl switches audio output to the jack port, on another
the command echo pin 33,12,2 > /dev/audioctl does the trick. It varies from
machine to machine, you can figure out the correct command by analyzing the
output of cat /dev/audiostat. This can be a bit daunting, but don't panic, just
look for words such as jack, speaker, out, pin, and experiment. Don't worry,
the machine will not blow up if you get it wrong ;)
You might find some of the following functions helpful, keep in mind though
that some specifics here are hardware dependent:
fn volume{ echo master $1 $1 > /dev/volume }
fn headphones{ echo pin 21 > /dev/audioctl ; volume 40 }
fn speaker{ echo pin 20 > /dev/audioctl ; volume 80 }
fn mute{ volume 0 }
fn pause{ stop pcmconv | rc }
fn resume{ start pcmconv | rc }
fn skip{ kill pcmconv | rc }
Of course, there are more user friendly 3rd party utilities that can help you
out. jacksense tries to automatically switch between output pins, whenever you
plug in a headset. And volume.c from the 9front extra repository, gives you a
simple button for adjusting output volume.
Technically you can play a raw audio file just by running cat file > /dev/audio
, or you can decode it first: audio/mp3dec < file.mp3 > /dev/audio. But 9front
includes a userfriendly shell script that makes life much easier: play file.mp3
.
Doing audio recording is theoretically possible in 9front, you first need to
redirect the correct pins as in the above example to set audio for input
instead of output, and then a read from /dev/audio will record sound. Eg. cat <
/dev/audio > file, or oggenc < /dev/audio > file.ogg. However, I have not been
able to make this work in practice on my test machines, but maybe you will have
better luck then I did.
The classic way to play music in Plan 9, was using the archaic juke player. You
first needed to write a fairly verbose database of your audio files though (see
juke(7) if you really want to do this). Thankfully, 9front recently replaced
this rusty jukebox with a cool new audio player called zuke. It easy and
pleasant to use:
; audio/mkplist $home/music/myalbum > $home/lib/plist/myalbum
; audio/zuke < $home/lib/plist/myalbum
[juke]
Video
For the longest time there were no video playing options at all in Plan 9, but
recently a video player called treason has been written by the progressively
treacherous 9front developers. The video player has limited capabilities, it
cannot skip back and forth, and worse, cannot scale the video in any way. It
would have been nice if we could manually set a lower screen resolution; by
running aux/vga -m vesa -l 1024x768x16 for instance, before playing a 480P (DVD
quality*) movie, to watch it fullscreen. But that will not work on all video
cards, such as, notably, my video card, but maybe you'll have better luck. (see
the Game Emulators section for some tips) In any case, fullscreen or not, you
can watch Plan 9 in Plan 9 - how cool is that!
Install the video player Treason from the developers website:
; mkdir /tmp/treason
; cd /tmp/treason
; hget https://ftrv.se/_/treason.gz | gunzip | disk/mkext -d .
; ./treason/install.rc
; treason der_film.mkv # watch some vids :)
[treason]
Youtube
Oh yes. With treason installed, you absolutely can watch Youtube videos in Plan
9. But first you need to install a Youtube downloader:
install nvi, similar to youtube-dl in unix:
; cd /tmp
; git/clone https://git.sr.ht/~ft/nvi
; cd nvi
; mk install
make some convenient wrapper scripts:
!/bin/rc
ytaudio - play audio only from youtube video
nvi -A 251 -a /fd/1 $1 |
mcfs -t audio |
audio/opusdec > /dev/audio
!/bin/rc
ytlow - play low quality youtube video
nvi -V 18 -v /tmp/vid.mp4 $1 &&
treason /tmp/vid.mp4 &&
rm /tmp/vid.mp4
!/bin/rc
ythigh - play high quality youtube video
nvi -a /tmp/audio -v /tmp/video $1 &&
treason -a /tmp/audio /tmp/video &&
rm /tmp/audio /tmp/video
You can now play the audio track of Plan 9 from Outer Space with ytaudio
qsb74pW7goU, watch it in low quality with ytlow or high quality with ythigh.
You can also add a plumbing rule to automatically play a youtube URL, just add
this to $home/lib/plumbing before the include basic line:
play youtube videos
type is text
data matches 'https://(www.)?youtube[^ ]+'
plumb start window ytlow ''''$0''''
If you now right click on https://www.youtube.com/watch?v=qsb74pW7goU in acme,
or run plumb with that argument (you need to put it in quotes since '=' has
special meaning for the shell), you'll see a nice low res video of a low
quality movie.
PS: As mentioned, adventuresin9 is a nice channel if you're looking for YouTube
videos about 9front!
Graphics
[photograph]
Viewing Images/Documents
There is only one program to display images and documents alike, and that is
page. It is a fantastic application, despite lacking support for some documents
types such as DOCX or ODT, and poor support for others such as Epubs. PDF's,
old Microsoft Office documents, images and other simple formats usually work
though.
Reading Comics
Comic books are often distributed as CBR or CBZ files, these are just rared or
zipped images, so to read them unrar (you can get unrar in 9fronts extra
repository - go is a dependency) or unzip the file, and then view the extracted
images in page:
; unzip -af voyage_to_venus_1.cbz
; lc voyage_to_venus_1
001.jpg 018.jpg 035.jpg 052.jpg 070.jpg 087.jpg
002.jpg 019.jpg 036.jpg 053.jpg 071.jpg 088.jpg
003.jpg 020.jpg 037.jpg 054.jpg 072.jpg 089.jpg
...
to view all of these, starting with 001.jpg:
; page voyage_to_venus_1
Creating Images
paint is available, though you would be hard pressed to use it for anything but
kindergarten art. resample(1), crop(1) and rotate(1) on the other hand, are
useful little tools for image manipulation, see their manpages for more
information. Another good alternative for image manipulation (as in ImageMagick
not as in PhotoShop), is pico9, available in the 9front extra repository. It's
still in the early stages of development, but it's looking good!
Taking a Screenshot
Not only is there a file in /dev for your window text, but there is also a file
for your window screen, /dev/window. To take a screenshot of your current
window, you can run this command: cat /dev/window > windowdump ; page
windowdump. To take a screenshot of your entire screen, do this: cat /dev/
screen > screendump.
These images are saved in the native Plan 9 image format, which of course the
document/image viewer page can read. But if you want to use these images on
other operating systems, you should convert them to the more popular PNG or
JPEG formats: cat /dev/screen | topng > sshot.png or simply tojpg < /dev/window
window.jpg
As for taking a screenshot of a different window then the current one, take a
look at the rio scripting section above. You can also do this in a more
GUI-like fashion, if you install the 3rd party program vshot.
[screencast]
Screencasting
There is work in progress on a screen recording program for 9front, called wrec
, available in the extra repository. It can do simple screen capturing, but
doesn't record sound yet. PS: I recommend recording with very few frames per
second, eg wrec -f 3, for best results. If you want to scale down a GIF and
make it continually loop, as in the above example, you can export the file to a
UNIX machine with ImageMagick installed and run: convert -delay 20 -loop 0
-resize 600 screencast.gif small.gif.
Peripherals
[gadget]
USB sticks
In 9front USB sticks are automatically mounted in /shr, but if you need to
manually mount it, run ls /dev | grep sd before and after plugging in your
memory stick, to find its device name. Supposing it's sdUc59fd run the
following command to mount the memory stick in /n/dos: mount <{dossrv -s} /n/
dos /dev/sdUc59fd/dos, and unmount it with unmount /n/dos, see dossrv(4) for
more information. If the device doesn't show up in /dev after plugging it in,
there is either some hardware/driver issue, or the device uses a file system
that isn't supported. Traditionally only DOS and Plan 9 file systems have been
supported, but with the addition of ext4srv in the 9front extra repository, it
is also possible to work with Linux file systems.
NTFS (Windows file system) is not supported, so you might need to reformat your
memstick to FAT32 (DOS file system) before you can use it. Assuming it is still
called sdUc59fd, you can do so like this:
; disk/fdisk /dev/sdUc59fd/data
p # print a table of partitions
? # get help instructions
d p1 # delete a couple of partitions
d p2
a p1 # add a new partition
1 # just follow suggested size
971
t p1 # set partition type
? # list available types
FAT32
w # write and quit
q
; disk/format -d /dev/sdUc59fd/dos
CD/DVD/BD's
To mount an iso image in /n/iso run the command mount <{9660srv -s >[0=1]} /n/
iso /path/to/your/cdrom.iso. This may look cryptic, but it's actually very easy
to work with CD/DVD/BD's in Plan 9 (see cdfs(4)), the following demonstration
shows how to mount an audio CD (you only need to specify the device if it isn't
/dev/sdD0), play it, and rip it:
; cdfs -d /dev/sdE1
; cat /mnt/cd/a* > /dev/audio
; cp /mnt/cd/a* /tmp/songs
You might find these custom functions helpful too:
fn mem{ mount <{dossrv -s >[0=1]} /n/dos $1 }
fn iso{ mount <{9660srv -s >[0=1]} /n/iso $1 }
fn eject{ echo eject > /mnt/cd/ctl }
fn cdfs{ /bin/cdfs -d /dev/sdE1 }
fn cddb{ # query the internet CD database
cdfs
grep aux/cddb /mnt/cd/ctl | rc
}
fn rip{ # rip a CD and convert it to ogg
cdfs
for(t in /mnt/cd/a*) audio/oggenc < $t > `{basename $t}^.ogg
}
The next example shows how to burn an audio CD. Simply change 'a' for 'd' to
burn a data disk (DVD's and Bluerays are always data disks). The last command
fixates the disk, which is not necessary on rewritable CD's or data disks:
; cdfs -d /dev/sdE1
; cp /tmp/files/* /mnt/cd/wa
; rm /mnt/cd/wa
Printers
Let me save you a lot of trouble: put LPDEST=stdout in your $home/lib/profile,
now lp will print its postscript to standard output. You can convert these PS
files to PDF if you want, then copy or email them to a Windows/UNIX machine,
and print out a hardcopy from there:
; lp doc.html | ps2pdf > doc.pdf
; doctype doc.ms | rc | lp | ssh unixmonster 'cat | lpr'
or from drawterm (os lets you run a host command)
; doctype doc.ms | rc | lp | os lpr
Games and other Fun Stuff
[ghastly_ga]
Gaming is a potentially contentious topic when it comes to computers. Although
massively popular of course, nothing is more detrimental to productivity
(except a modern web browser perhaps). So the trick to creating a good computer
game, is making it fun enough to distract you for a few minutes of
recuperation, but boring enough that it doesn't keep you from doing important
work. By this definition Plan 9 has a few "good" games.
Included Games
Plan 9 comes with a collection of games in /bin/games. My favorites include:
• games/sudoku
• games/mahjongg
• games/sokoban
• games/mines (9front only)
Included Game Emulators
In 9front you will also find a number of emulators in the game directory,
assuming you can get hold of a legal copy of the Mario World ROM for instance,
you can play it like so: games/snes -ax 4 mario.sfc (beware though - some of
these oldschool games can be dangerously fun!)
• games/nes Nintendo
• games/snes Super Nintendo
• games/gb GameBoy
• games/gba GameBoy Advanced
• games/md Sega Mega Drive
• games/c64 Commodore 64
[games]
In my first attempts at playing these games, the experience was not perfect. My
rusty old ThinkPad struggled to get good audio out of these games, and as
mentioned in the Video section, my video card flat out refused to set the
screen to a lower resolution. I managed to circumvent both issues by PXE
booting 9front in my more powerful desktop machine. It works great as a Plan 9
movie and gaming console, and I have a nice little launch script for Zelda:
!/bin/rc
zelda - launch zelda fullscreen
usage: zelda
depends: gaming "console" and zelda rom
aux/realemu
aux/vga -m vesa -l 1024x768x16
window -r 0 0 1024 768 games/snes -ax 4 $home/games/snes/zelda.sfc
while(sleep 5){
if(! ps | grep -s snes) aux/vga -m vesa -l 1920x1080x16
}
3rd Party Games
3rd party games, or indeed software, for Plan 9 is rare. But there are
exceptions, some good ones are 2048, hack9 and snake.
Install 2048 and hack9 game from the 9front extras:
; cd /tmp
; 9fs 9front
download packages
; cp /n/extra/src/2048.c .
; tar xzf /n/extra/src/hack9.tgz
compile and install 2048
; 6c 2048.c
; 6l 2048.6
; mv 6.out $home/bin/amd64/2048
compile and install hack9
; cd hack9
; mk install
Install snake game from Bell Labs contrib repository:
; cd /tmp
; 9fs sources # download file
; cp /n/sources/contrib/john/snake.c .
; 6c snake.c # compile and install it
; 6l snake.6
; mv 6.out $home/bin/amd64/snake
[hack9]
Edutainment
There are a few educational applications in Plan 9, such as scat and map (to
use the later you'll need to download additional data, see /lib/map/README).
After finding the coordinates of my city on Wikipedia for example, I can draw a
basic map of Northern Norway: map perspective 1.01 -o 67.2827 -14.3751 | plot
To draw Africa, we can use the coordinates of a central city, like Bangui: map
perspective 2 -o 4.22 -18.35 | plot (the first number is the distance from the
map, the higher the number the more you'll see) I can also add the coordinates
of my city, 67.2827000000 -14.3751000000 150 (the last number is meters above
sea level), to /lib/sky/here and get fairly accurate astronomical data. astro
will print when the Sun sets and rises etc (quite useful in Northern Norway
with the midnight sun and all), and you can use scat to draw a star chart. To
demonstrate (more examples in the man page):
; scat
2024 8 23 06:56:11 GMT 6h01m55s +67°16'57" -14°22'30" 150
moon # find the moon
moon 1h09m25.0s +7°51'08.0" 258.06° 13.44° 16'30.2" 0.6229
expand 6 # zoom out a bit
scat: NGC database not available
0h45m00.0s to 1h35m00.0s
+1°00'00.0" to +14°00'00.0"
728 items
keep m <6 # only show what you can see with your naked eye
8 items
add moon # the last command removed the moon, add it again
9 items
plot # draw star chart
These programs use plot to draw the graphics. Other programs, such as graph and
proof (a precursor to page) use it too. It's very UNIX'y to build utilities on
tools that already exist, and these programs are certainly interesting. Yet,
years of neglect have rendered them mere toys today.
[eclipse]
Many of the programs in /bin/games are also more edutainment then actual games.
This includes simulators such as life, galaxy and timmy. You also have some
very computer science nerdy "games" such as blit (more on this later) and mix
(last four examples are 9front specific).
Arithmetic
The classic bsdgames collection provides UNIX with many simple edutainment
programs, most of which are not available in Plan 9. No matter, we can just
write them from scratch. Or at least, I can demonstrate how to implement some
of the basic ones. Who knows, maybe these simple scripts will inspire you to
write an actually entertaining game yourself :^)
!/bin/rc
arithmetic - basic arithmetic quiz
usage: arithmetic [-q n][-r n][-o '+-*/%^']
set some default values
rfork e
right=0
wrong=0
questions=20
range=1 # in digits
operands='+-'
start=`{date -n}
parse optional flags
for(i in $*){
switch($i){
case -q
questions=$2 && shift 2
case -r
range=$2 && shift 2
case -o
operands=''$2'' && shift 2
}
}
rnum: generate a random digit based on cpu clock
fn rnum{
awk '{ print $2 }' /dev/time | sed 's/.*(.)$/\1/'
}
ask math questions
opleft=$operands
for(i in `{seq $questions}){
# generate random math puzzle
a=`{rnum}
b=`{rnum}
if(test $range -gt 1){
for(i in `{seq `{echo $range - 1 | bc}}){
a=`{echo $a^`{rnum}}
b=`{echo $b^`{rnum}}
}
}
if(~ $#opleft 0) opleft=$operands
if not{
opused=`{echo $opleft | sed 's/^(.).*/\1/'}
opleft=`{echo $opleft | sed 's/^.(.*)/\1/'}
}
echo $a $"opused $b
correct=`{ echo $a $"opused $b | bc }
answer=`{ read }
# evaluate given answer
while(! ~ $answer $correct){
if(echo $answer | grep -s '^[-+]?[0-9]+$'){
echo What?
wrong=`{ echo $wrong + 1 | bc }
}
if not echo Please type a number '(no decimals!)'
answer=`{ read }
}
echo Right!
right=`{ echo $right + 1 | bc }
}
print result of math quiz
finish=`{date -n}
time=`{echo $finish - $start | bc}
total=`{echo $right + $wrong | bc}
timepq=`{echo $time / $total | bc}
if(~ $right 0){ prct=0% }
if not prct=`{ echo 'scale=2 ; '$right' / '$total'' |
bc | sed 's/\.//' | sed 's/$/%/' }
echo -n $right right, $wrong wrong '('$prct' correct)'
in $time seconds '('$timepq's per answer)'
Quiz
quiz is another simple classic from bsdgames, it just asks you a bunch of
questions and keeps track of your progress. Originally the UNIX quiz programs
could ask you some fairly dated questions about geography, Star Trek or the ed
editor, but the real beauty of this program is that you can write your own quiz
files. In theory you could even use this program for serious purposes, such as
training vocabulary or prepping for an exam.
!/bin/rc
quiz - ask questions and look for correct answers
usage: quiz [-as][-q questions][file]
#
bug: case is normally ignored, but not for exotic unicode characters, this is
a grep bug.
bug: special characters in the correct answers must be escaped (eg. \?\!)
variables
rfork e
ifs='
'
dir=$home/lib/quiz
is=(Correct answer is)
right=0
wrong=0
printanswer=no
if(~ $1 -a) printanswer=yes && shift 1
silenterror=no
if(~ $1 -s) silenterror=yes && shift 1
questions=20
if(~ $1 -q) questions=$2 && shift 2
parse args
if(~ $#* 0) ls -p $dir && exit
if(~ $#* 1) file=$dir/$1
if not echo usage: quiz [-as][-q questions][file] && exit
if(test `{cat $file | wc -l} -le $questions) questions=`{cat $file | wc -l}
ask questions, and check answers
for(i in `{sed -e '/^$/d' -e '/^#/d' $file | shuf | sed ''$questions'q'}){
question=`{ echo $i | awk -F@@@ '{ print $1 }' }
if(echo $question | grep -s '^cmd ')
eval `{ echo $question | sed 's/^cmd //'}
if not echo $question
correct=`{ echo $i | awk -F@@@ '{ print $2 }' }
correct_answer=`{ echo $i | awk -F@@@ '{ print $3 }' }
if(~ $#correct_answer 0) correct_answer=$correct
answer=`{ read }
if(echo $answer | grep -si '^'$correct'$'){
if(~ $printanswer yes) echo -n Right! $"is $"correct_answer
if not echo -n Right!
right=`{ echo $right + 1 | bc }
}
if not{
if(~ $silenterror yes) echo -n Wrong!
if not echo -n Wrong! $"is $"correct_answer
wrong=`{ echo $wrong + 1 | bc }
}
read
}
calculate results
if(~ $right 0){ prct=0% }
if not prct=`{ echo 'scale=2 ; '$right' / '$questions'' |
bc | sed 's/\.//' | sed 's/$/%/' }
if not echo $right right, $wrong wrong '('$prct' correct)'
This program expects a plain text database in $home/lib/quiz with two,
optionally three, fields separated by @@@. The fields are: question, answer.
The correct answer can be written as a regex, to allow for variations, if so
then a third field must also be written, the default answer. Here is what the
end of my $home/lib/quiz/capitols file looks like:
Ukraine@@@Kyiv|Kiev@@@Kyiv
United Kingdom@@@London
Uruguay@@@Montevideo
Uzbekistan@@@T[oa]shkent@@@Toshkent
Vanuatu@@@Port Vila
Venezuela@@@Caracas
Vietnam@@@Ha ?Noi@@@Ha Noi
Yemen@@@[ŞS]an'?a'?@@@Şan'a'
Zambia@@@Lusaka
Zimbabwe@@@Harare
Touchtype
Learning to touchtype is a must for any serious computer user, and even for the
seasoned sysadmin it is a skill that one might want to brush up on from time to
time. There are elaborate touchtyping tutors in UNIX, such as ktouch, but the
basic method of learning this skill is fairly simple: Print out a picture of
your keyboard layout and stick it to the wall, as you type away, look up at the
picture not down at your keyboard (ideally you should also place your fingers
on the middle row, with your index fingers on the two keys which have little
bumps on them). This is hard to do in the beginning, but if you keep at it, you
will gradually learn to touchtype. The following script will not take away the
pain and discipline required to learn this skill, but it can help you track
your progress. Just retype each line that you are given, but do not hit
backspace and correct your mistakes, just keep on typing. When you are done the
script will tell you how well/bad your typing skills are.
!/bin/rc
touchtype - check your typing speed and accuracy
usage: touchtype [ file ]
choose input sample
rfork ne
tmp=/tmp/touchtype-$pid
out=/tmp/touchtype-out-$pid
fortune > $tmp
if(~ $#* 1) cat $1 > $tmp
ifs='
'
do some touchtyping
start=`{date -n}
for(line in `{cat $tmp}){
echo $line
read >> $out
}
stop=`{date -n}
calculate results
time=`{echo $stop - $start | bc}
char=`{cat $tmp | wc -c}
speed=`{echo '('$char' / '$time') * 60' | bc}
err=`{cmp -l $out $tmp | wc -l}
if(~ $#err 0) prc=0
if not prc=`{echo 'scale=2 ; '$err' / '$char'' | bc | sed 's/\.//'}
print results
rm $tmp $out
echo
echo 'RESULT (<2% errors and >200 c/m is good):'
echo your write speed is $speed c/m with $prc^% errors
Playing With Telnet
Believe it or not, but there are actually a lot of fun stuff to be done with
telnet, even in 2021! Not least of which is playing MUD's, multi-user-dungeons
are still alive and kicking. You can find a list of popular ones on http://
mudconnect.com. Here are some fun telnet examples (PS: run vt first for a
better user experience):
; telnet discworld.starturtle.net # Play the Discworld MUD
; telnet towel.blinkenlights.nl # Watch Star Wars IV in ASCII
; telnet twenex.org # Login to a shell server with a handful of TTY games
Miscellaneous Fun
You can do a lot of fun stuff on Plan 9 that do not strictly fall into the
category of "gaming". A classic example is fortune, which will display a random
quote. 9front also ships with troll and theo, which does much the same thing,
but are more specific. Plan 9's fortune is also handy for printing a random
line form an arbitrary file (eg. play `{fortune playlist}). Another fun program
is games/festoon, which generates a gibberish troff document, you can for
instance use it like so: games/festoon -pet | pic | eqn | tbl | troff -mm |
page
Some of the programs in /bin/games are more or less screensavers, such as
juggle and catclock. 9front also throws in mole and packet, which fit this
category. Lastly, there is a port of classic UNIX screensavers in the 9front
extra repository, called xsr.
Obscure Operating Systems
[old_tech]
We have already touch on vmx in the virtualizing section above, which let you
run things like Linux, and plausibly Windows, in 9front. But you can also run a
few obscure operating systems more natively, and these systems may be of
special interest, and provide a lot of fun, for a Plan 9 fan:
Inferno
The Inferno project was started a few years after Plan 9 was initially
released, and it was more or less developed in tandem at Bell Labs, with the
same group of developers. The operating system share much in common with Plan
9, you will find acme and other similar programs, and it shares the exact same
file system protocol (although it is referred to as styx, not 9p, in the docs
for historic reasons). Since everything in Inferno is a file, you can
seamlessly share devices and other resources between it and Plan 9.
This last point is especially valuable, because Inferno was designed to run on
top of other operating systems. It can run on virtually any (old) UNIX system,
Plan 9 of course, and even in old Internet Explorers! Inferno presents the
network, audio, memory etc. of these systems as regular files, and thus provide
an elegant bridge between a Plan 9 system and, say, a Linux or FreeBSD box. It
can also run in as little as 256 Kb of memory, a quarter of a Megabyte! So it
is well suited for embedded applications.
Sadly though, Inferno suffers badly from neglect and code rot. Audio will not
work today, and with it, any of the multimedia applications that Inferno
provides. Worse, Inferno is only supported on 32-bit systems, and it's getting
increasingly difficult to even build it on modern systems. There has been some
forks of Inferno in later years, such as purgatorio and 9ferno, all the same,
the project seems very dormant. You can install and run the original Inferno in
Plan 9 (32-bit) like so: (you might have more luck compiling it in 9legacy,
since that OS is also reassuringly neglected)
; cd $home/src
; git/clone https://bitbucket.org/inferno-os/inferno-os
; mkdir /usr/inferno
; dircp inferno-os /usr/inferno
; cd /usr/inferno
; path=(/usr/inferno/Plan9/386/bin $path)
; mk install
install a new user
; mkdir tmp
; mkdir usr/$myuser
; dircp usr/inferno usr/$myuser
run inferno and start a desktop
; emu
; wm/wm
[inferno]
Be sure to read the papers in the doc directory here, especially bltj.pdf,
sh.pdf, descent/descent.pdf, and limbotk/tk.pdf, which introduces the Inferno
operating system, its rc-inspired shell, its unique programming language,
Limbo, and the Tk GUI toolkit for it. Inferno was written in an entirely new
programming language, Limbo, a precursor to Go. Unlike Plan 9, its approach to
GUI's is also much closer to traditional systems. So if you have experience
with Tk, or really any other toolkit in UNIX or Windows, you will find it quite
easy to develop graphical programs in Inferno. Btw, the default startup menu is
quite scarce, but you will find many additional GUI programs under /dis/wm, and
you can modify the startup menu configuration file in /lib/wmsetup.
Inferno was intended as a commercial product, and it has a sort of Windows'y
feel to it. And yet, despite deep differences, it is very reminiscent of Plan
9. It is an interesting blend, and a fun programming environment. But be
prepared for bugs and limitations, as its name suggests, the project has been
dead for a long time (in contrast to Plan 9, which is undead).
PS: You can get around many of the limitations in Inferno with the os command,
it lets you execute a host program from within Inferno. For example, Inferno
does not include awk, tar or lp (lpr in UNIX), but you can easily write wrapper
functions that use these host commands. You might also want to add some startup
shortcuts to your local $home/lib/wmsetup. (shells will only use $home/lib/
profile with the -l (ell) flag)
inferno startup shortcuts for plan 9, adjust to suit your needs:
EMU=(-g1600x900 -C x8r8g8b8 -f /fonts/vera/veramono/veramono.12.font -c1)
fn inferno{ /usr/inferno/Plan9/386/bin/emu wm/wm wm/logon -u myuser }
to halt inferno, run this in an inferno shell
; shutdown -h
adding awk to inferno (do this within inferno):
; mkdir $home/dis
; echo bind -b $home/dis /dis >> $home/namespace
; touch $home/dis/awk
; chmod +x $home/dis/awk
then, you can add this to $home/dis/awk:
!/dis/sh
awk - a wrapper for awk on host
usage: awk '{ cmd... }'
bugs: does not support awk flags
if {~ $#* 0} { file = /fd/0 } { file = $2 }
os -d $emuroot^`{pwd} awk $1 $file
UNIX V8
In the late 80's, the designers of UNIX continued to work on their operating
system, and developed Research UNIX Version 8 through 10, before they went on
to develop Plan 9. You can see the prototypes of many Plan 9 ideas in these
early UNIX systems. For example, mux, jim and face, are essentially the
prototypes for rio*, sam* and faces in Plan 9, you will find early versions of
plot and proof too. You can run early editions of UNIX with the SIMH emulators,
using the vax780 emulator for V8 and the BSD's, and the pdp11 emulator for the
earliest editions of UNIX. To install and run V8:
first, install the simh emulators
; cd /tmp
; 9fs 9front
; tar xzf /n/extra/src/simh.tgz
; cd simh
; plan9/build_all
; mkdir $home/bin/$objtype/simh
; dircp BIN $home/bin/$objtype/simh
download v8 and run it
; hget http://9legacy.org/download/unix/v8-simh.tar.bz2 | bunzip2 | tar x
; cd v8-simh
; vt
; simh/vax780 v8.ini
login: root
[blit]
Using the ANSI terminal vt is not a hard requirement, but it provides a better
experience. Once the server is running, right click and choose "raw". This will
prevent text from echoing twice, and it will allow you to use key combinations,
like Del and Ctrl-D (otherwise Plan 9 will interpret these signals). And yes,
early Unixes used Delete to kill a process, just like Plan 9 does. After
halting the system (see notes below), right click and choose "cooked". When you
now hit the Delete key, Plan 9 will stop the VAX emulator. Lets add a new user
to our V8 system:
install a new user
echo myuser::8:4:mh1092,m069:/usr/myuser: >> /etc/passwd
mkdir /usr/myuser
/etc/chown myuser /usr/myuser
exit
login: myuser
set up your environment
$ cat << eof > .profile
TERM=blit # or vt100
export TERM
PATH=$PATH:/etc:/usr/games:/usr/blit/bin:$HOME/bin
export PATH
eof
halt the system - preferred way (old v7 style also works)
$ su
kill 1
/etc/umount -a # v7 style: sync; sync; sync
/etc/halt
you can now safely kill the vax780 emulator
As you can see, the system is quite similar to Plan 9 in its simplistic
approach to user management and shutdown procedures. We will get back to the
TERM value later, but basically, if you plan on connecting to V8 with vt, or a
UNIX terminal, use vt100. And this is the value you want to set, if you are
running V7 or one of the BSD's in SIMH. Setting the TERM value will allow you
to use programs like vi and rogue. Setting the PATH variable will make it
easier to launch programs, you can run chown for example, rather then the more
accurate /etc/chown. If you don't already have it, I highly recommend that you
get The UNIX Programming Environment by Kernighan and Pike. This is the book on
UNIX, whether you use V8 or a Mac or anything in between! Also, if you have the
interest, you can look up the abstract papers provided with UNIX Version 7 and
10, referred to as "Volume 2" of the manual. They provide some historic context
and useful hints for V8.
In the olden days, UNIX ran on a big server somewhere in the basement, with
multiple users connected to it via diskless terminals. You can simulate this by
opening up several windows and connect to the server via telnet: telnet tcp!
<mymachine>!8888, just make sure to change <mymachine> to your actual computer
name (eg. "cirno", not "localhost"). Since these terminals are stateless, you
don't need any shutdown procedure, just delete the window. The server however
runs a file system, so it should be halted with the above instructions.
As mentioned though, V8 was meant to be a graphical system, and it included a
window manager, graphical text editor and other pointy-clicky things. Bell Labs
created their own graphical terminal for V8, called the Blit (originally the
Jerq, but for some reason management had problems with that name). To use
graphical programs in V8, you need to connect to a V8 server with a Blit
terminal. 9front includes a blit emulator, and you can connect it to a V8
server like so: games/blit -b 19200 -C 000000,00ff00 -t tcp!<mymachine>!8888
(the first two flags here are optional). You can start the window manager with
/usr/blit/bin/mux, or if you have set your PATH correctly, just mux.
If you access V8 with this blit emulator, you want to set the TERM variable to
blit. However, programs such as vi and rogue will not work in mux. To run such
programs you first need to quit the window manager with mux exit, and then run
these programs in the text terminal. You will find some fun graphical programs
under /usr/blit/bin, including demo pacman and crabs. The later spawns a bunch
of tiny crabs that wonder about the screen and randomly eats chunks of your
windows. According to Rob Pike it was a favorite pun among the developers to
schedule such a program to run 30 minutes into the future, whenever some boss
at Bell Labs needed to use the computer for an important meeting. Enjoy :)
Office
[document]
There are a great many office suits on most operating systems, and other
utilities besides too numerous to count. So many are the choices in fact that
it's easy to forget that "office" is just a fancy word for working with text.
Plan 9 does not delude it's users: You need to be a proficient reader and
writer to use the system, and you need to organize and manage your files. In
other words, you need to have essential office skills to use the system well.
Reading Office Documents
As far as it's up to you, I'm sure all of your documents are plain text as a
matter of course. Plain text is editable, searchable, pipeable, programmable.
You can mangle it freely with standard tools such as grep, sed and awk, and it
doesn't require a flippin Terrabyte of diskspace. In Plan 9 text is even more
powerful, it's always unicode, it's plumbable, acmeable, zeroxable,
yesterdayable, snarfable and devable (yes, these are "real" adjectives in Plan
9). It's the magic goo that holds everything together, much like in the real
world. You would be insane not to write documents as plain text! But sadly it's
not always up to you. Your pesky boss may send you important Word documents,
with little to no regard for your peculiar taste in operating systems. Don't
panic! Many office documents are readable with page (naturally HTML files can
be read with mothra). Documents that aren't handled by page, such as DOCX or
ODT, can easily enough be converted to PDF before importing them to your Plan 9
box (assuming you don't run Plan 9 on all your machines).*
Reading Epubs
In theory, page can handle Epubs, but in my experience it can't. Epubs are
basically just zipped HTML files, so it is possible to unzip them, search
around for a "toc" (table of contents) file to find what files constitute what
chapters, and then read them one by one in a web browser. The following script
automates that process:
!/bin/rc
epub2html - convert epub to html
usage: epub2html file.epub
bugs: only one epub at a time
set some defaults
rfork e
cwd=`{pwd}
fn usage{
echo Usage: epub2html file.epub >[1=2]
exit usage
}
if(! ~ $#* 1) usage
file=$1
if(! ~ $file /*) file=`{cleanname $cwd/$1}
if(! test -f $file && ! ~ $1 *.[Ee][Pp][Uu][Bb]) usage
name=`{basename $1 | sed 's/\.[Ee][Pp][Uu][Bb]//'}
dir=$name^_files
determine directory name of toc file
fn ops{
ops=`{ls -p $1 | grep -i '^o.*ps'}
if(~ $#ops 0) echo $1
if not{
toc=`{ls -p $1/$ops | grep -i 'toc.ncx'}
if(~ $#toc 0) echo $1
if not echo $1/$ops
}
}
extract epub and chapter information
mkdir -p $dir && cd $dir
unzip -af $file >/dev/null >[2=1]
ops=`{ops $cwd/$dir} && cd $ops
cat [Tt][Oo][Cc].[Nn][Cc][Xx] | sed -n '/<navPoint/,/<\/navPoint/p' |
sed -n 's/.*<text>(.*)<\/text>.*/\1/p' > chaps
cat [Tt][Oo][Cc].[Nn][Cc][Xx] | sed -n '/<navPoint/,/<\/navPoint/p' |
sed -n 's/.*src="(.*)".*/\1/p' | sed 's/%20/ /g' > links
generate html index
cat <<eof > $cwd/$name.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Contents</title>
</head>
<body>
<h1>Contents:</h1>
eof
for(i in `{seq `{cat links | wc -l}}){
link=`{sed -n $i^p links}
chap=`{sed -n $i^p chaps}
echo ' <a href="'$ops^/^$"link'">'$"chap'</a><br>' \
>> $cwd/$name.html
}
cat <<eof >> $cwd/$name.html
</body>
</html>
eof
This script works surprisingly well for my needs, but I cannot guarantee that
it will handle absolutely all Epubs gracefully. Feel free to expand or adjust
the script to suit your needs. To wet your appetite, I will add three
additional scripts based on epub2html. They are fairly self explanatory. The
last one, eread, is probably the most interesting; It extracts the epub
directly into private memory and reads the resulting html in mothra. Once you
exit the browser, the files are discarded. Thus it provides a fast way to read
epubs without messing with temporary files on disk. Of course, if you aren't a
lazy bum like me, you might want to patch up page so that it handles Epubs
correctly, instead of monkeying about with shell scripts ;)
!/bin/rc
epub2txt - convert epub to text
usage: epub2txt file.epub
depend: epub2html
set some defaults
rfork e
if(! ~ $#* 1) exit usage
keep=yes
name=`{basename $1 | sed 's/\.[Ee][Pp][Uu][Bb]//'}
if(! test -f $name.html){
keep=no
epub2html $* || exit $status
}
ifs='
'
convert extracted epub to text
$name.txt
for(file in `{awk -F" '/<a/ { print $2 }' $name.html})
html2ms < $"file | deroff | fmt >> $name.txt
if(~ $keep no)
rm -rf $name.html $name^_files
!/bin/rc
epub2pdf - convert epub to pdf
usage: epub2pdf [-k] file.epub
depend: epub2html
bugs: troff(1) cannot handle any and all fonts,
so expect to see Weinberger pinups pop up.
set some defaults
rfork e
if(! ~ $#* 1) exit usage
keep=yes
name=`{basename $1 | sed 's/\.[Ee][Pp][Uu][Bb]//'}
if(! test -f $name.html){
keep=no
epub2html $* || exit $status
}
temp=/tmp/epub2pdf-$pid
mkdir $temp
ifs='
'
convert extracted epub to pdf
for(file in `{awk -F" '/<a/ { print $2 }' $name.html})
html2ms < $"file >> $temp/out.ms
doctype $temp/out.ms | rc | dpost -f >[2]/dev/null |\
ps2pdf '-dCompatibilityLevel=1.4' > $name.pdf
rm -rf $temp
if(~ $keep no) rm -rf $name.html $name^_files
!/bin/rc
eread - read an epub directly in mothra
usage: eread file.epub
depend: epub2html, mothra
rfork ne
cwd=`{pwd}
name=`{basename $1 | sed 's/\.[Ee][Pp][Uu][Bb]//'}
extract epub to memory, then read it
ramfs -p; cd /tmp
epub2html `{cleanname $cwd/$1} || exit $status
mothra -a file://tmp/$name.html
Writing Office Documents
For all it's wondrous benefits, plain text documents has an obvious flaw: They
don't look good. If you need to write an article or even just a professional
looking letter, you need something a little more sophisticated then monospace
fonts. Troff is your friend (an ancient Plan 9 port of TeX is also available,
but you might want to check out KerTeX is you absolutely have to go down that
rabbit hole). Don't be too quick to dismiss this venerable old tool! While man
man will print a rather unimpressive monospaced manual, the command man -t man
| page, produces a much more professional looking document! Besides the man(6)
macros for writing manual pages, Plan 9 also includes the ms(6) macros for
writing generic articles and letters, naturally with full unicode support (a
feature either missing or clunky in UNIX Troff, not to mention TeX or DocBook).
You can also use the mpictures(6) macros for including images (these must be
converted to Postscript first, eg: jpg -9t < image.jpg | lp -dstdout > image.ps
) and the html2ms/ms2html commands for converting troff articles to/from HTML.
Here is a letter in troff (using ms macros), and a screenshot of the result:
.DS L
To: Archduke Poggle of Geonosis
23 Insectoid Str.
Hive
103133
GEONOSIS
From: Emperor Palpatine
Imperial Palace
P0 000001
Senate District
CORUSCANT
.DE
.SH
Dear Archduke
.PP
The so called
.I
undefeatable
.R
Death Star was blown to bits by a bunch of teenagers yesterday.
I must say I am disappointed!
We need to construct a new planet killer ASAP,
and this time lets try to avoid an
.B
Achilles heel
.R
in our design shall we?
.PP
I have some other ideas for further improvements.
First of all we need a
.I
menacing
.R
throne room with a view...
.DS
Yours truly,
Palpatine
.DE
[letter]
troff syntax is very simple, add a troff or macro command, such as .SH for a
section header or .PP for a paragraph on a line by itself, then the text
content after it. Technically these are ms macro commands, which differ
slightly from man macros, low level troff commands are written in lower case
(eg. .br or .bp to force a line break or begin a new page). You can also write
certain inline troff commands if you need to (eg. \fIitalics\fRroman\f(CW
constant-width fonts). But you don't need to know all that if you just want to
write a simple letter, in fact .SH and .PP will suffice, but see ms(6) if you
are thirsty for more.
Make no mistake, troff can be used to write highly professional documents. The
development section mentioned Francisco J. Ballesteros excellent book on Plan
9, it is worth mentioning that this book was written in Plan 9 using troff. A
less professional, but perhaps still useful example, is my article on Operating
System Complexity. You can compare this PDF with the ms source code for a taste
of what writing a troff document looks like.
One issue when working with troff is that you need to use a plethora of
different troff preprocessors, macro packages and what not, in order to compile
the source into a useful document. Run doctype myfile.ms to see what commands
are needed to convert the file into pure troff, this can then be read in page
or converted to useful formats. To illustrate:
; doctype myfile.ms
tbl myfile.ms | troff -ms -mpictures
; doctype myfile.ms | rc | page
; doctype myfile.ms | rc | dpost > myfile.ps # ei. postscript
; doctype myfile.ms | rc | dpost | ssh unixmachine 'lpr'
; doctype myfile.ms | rc | dpost | ps2pdf > myfile.pdf
; tbl myfile.ms | nroff -ms > myfile.txt
; ms2html < myfile.ms > myfile.html
Depending on your document, some of these conversions may not work very well.
Plain text and HTML conversions are often quite bad, but Postscript and PDF
should work well. If you work a lot on troff documents you may find it useful
to create some shortcuts, for example:
fn readms{ doctype $* | rc | page }
fn ms2pdf{ doctype $* | rc | dpost | ps2pdf '-dCompatibilityLevel=1.4' > out.pdf }
Tweaking Troff Macros
Now, to be clear, you don't want to write your documents in pure troff.
Friendly macro packages like ms are there for a reason! Nevertheless, there are
times when you actually need a more hands on approach. I regularly print and
send personal letters for instance. The default ms documents have a very small
font with wide margins. To make matters worse, the macro is not calibrated for
an A4 paper size, which is ubiquitous here in Europe. The net result is that my
ms letters have barely readable fonts with magnanimous margins all around.
Besides, I prefer Helvetica over the Times font, so lets change things up a
bit. Here is a very basic macro package that does the job:
.\" Basic A4 troff macros for personal letters
.\" automatically add margins at top and bottom of page
.de hd
'sp 0.4i
..
.de fo
'bp
..
.wh 0 hd \" run hd at start of page
.wh -0.6i fo \" run fo at bottom -0.6 inches
.\" set A4 paper size (8.3i x 11.7i), margins and text size,
.\" and redefine standard fonts to use Helvetica
.pl 11.7i \" page length
.ll 7.4i \" line length (8.3 - 0.4 - 0.5 (right margin))
.po 0.4i \" page offset (left margin)
.ps +2 \" point size (text size)
.vs +1 \" vertical (line) space
.fp 1 R H \" redefine font 1 (regular) roman
.fp 2 I HI
.fp 3 B HB
.\" add user friendly macros
.de SH \" section header
.ft 3
.ps +4
..
.de PP \" paragraph
.ps 12 \" reset text size and type
.ft 1
.sp 1 \" (vertical) space
.ti 2 \" temporary indent
..
.de R \" basic font macros
.ft R
..
.de I
.ft I
..
.de B
.ft B
..
As you can see, a troff macro command is defined within a .de CMD, .. block,
and comments begin with \". You can read the troff paper in /sys/doc/troff.ps,
to learn more of whats going on. And you might want to look at the ms macro
package in /sys/lib/tmac/tmac.s, and perhaps add a few more commands to this
bare bones example. Plan 9 troff comes with only a limited number of installed
fonts, peek at /sys/lib/troff/font/devutf/shell.lib to look at your options. If
you need custom fonts, colors and URL links in your documents, you really need
to bite the bullet and use a UNIX version of troff. Gavin Freeborn has some
nice youtube videos on the subject, if you're interested.
This macro package uses slightly larger fonts then ms, which has a default
point size of 10, and it hugs the corners of the paper much closer (about a
centimeter of margins on all four sides). To use the macros in my documents, I
can add .so mymacro.a4 to the top of my letter, and then read it with troff
myletter.a4 | page. But since I'll use it frequently, it is more convenient to
install it system wide: cp mymacro.a4 /sys/lib/tmac/tmac.a4. I can now drop the
.so mymacro.a4 line, and compile my letter like so: troff -ma4 myletter.a4 |
dpost | ps2pdf '-sPAPERSIZE=a4' > myletter.pdf. (note that Ghostscript, used by
ps2pdf, needs to know the paper size as well as troff)
Spellchecking
The spellchecker spell(1), and the acme equivalent aspell, is a simple but
useful tool for spellchecking English text (sadly it does not support user
supplied dictionaries). Speaking of which, dict(7) is an excellent English
dictionary, somewhat equivalent to WordNet in UNIX. To use this tool you need
to install some files first, see the README's in /lib/dict for instructions.
There is precious little support for non-English languages in any operating
system, but you can use various strategies for spell checking at least, as an
example consider these functions for spell checking Norwegian:
fn lower{
tr A-ZÆØÅ a-zæøå
}
fn words{
tr -c 'a-zæøåA-ZÆØÅ''' '
' | sed 's/''//g' | sort | uniq
}
temp=/tmp/dict-$pid
dict=/lib/words.no # Norwegian dictionary
lodict=`{basename $dict} # Local Norwegian dictionary
fn nolook{
look $* $dict
}
fn nospell{
if(test -f $lodict) dict=$lodict
for(word in `{deroff $* | lower | words | comm -13 $dict -})
if(! grep -s '^'$word'$' $dict) echo $word
}
fn noaddword{
if(test -f $lodict) dict=$lodict
for(word in $*) echo $word >> $dict
words < $dict > $temp && mv $temp $dict
}
fn nomkdict{
comm -12 <{deroff $* | lower | words} $dict >> $lodict
for(word in `{deroff $* | lower | words | comm -13 $lodict -})
if(! grep -s '^'$word'$' $dict) echo $word >> $lodict
words < $lodict > $temp && mv $temp $lodict
}
These functions require you to have a dictionary of correctly spelled Norwegian
words in /lib/words.no. Assuming you have a UNIX machine nearby with the
Norwegian wordlist for aspell installed, you can import the dictionary like so:
ssh myunixpc 'aspell -d nb dump master | aspell -l nb expand' | tcs -f 8859-4 |
sort > /lib/words.no (change "nb" here if you need another language, eg. "fr"
for French). The lower and words shorthands take the special Norwegian letters
æøå into account. nolook is just a shorthand for Norwegian look(1).
Much like spell, nospell breaks up your document into individual, unique words
stripped of any troff syntax, and prints any word not found in the dictionary.
(unfortunately comm doesn't handle non-English letters well, which is why we
need an extra grep line to catch words that contain the Norwegian letters æøå)
To add custom words to the dictionary, use noaddword. You'll note though that
nospell will use a local dictionary file, if it exists. Run nomkdict *.ms to
populate such a local dictionary, ~/dict.no, with words matched in the global
dictionary, /lib/dict.no. You can now freely noaddword's to the custom list,
without effecting the system dictionary, and spellchecks will be hundreds of
times faster, since the local dictionary is honed to the vocabulary of your
project files.
These custom tools are crude, in particular they do not handle suffixes/
prefixes, so you need a large global dictionary before they become useful. For
instance, the document you are reading now contains some 4113 unique English
words. spell will flag 1053 of them as spelling errors.* If you use the above
strategy coupled with the default dictionary in /lib/words, containing some
30,000 words, you will get a whooping 2174 errors. Using the English aspell
dictionary however, containing some 120,000 words, you will only get 853 errors
(the default Plan 9 dictionary intentionally omits suffixes/prefixes). Of
course all of these errors are false positives. (I hope!)
By comparison LibreOffice will give you 828 unique false positives, which is
about as lousy. The spellchecking mechanics of this massive office suit is
certainly more attractive then our crude shell script, but is it necessarily
"better"? Does it improve your spelling skills to right click in a GUI a
thousand times, rather then manually retyping the correct words one by one? How
easy is it to customize the tool and adapt it to your peculiar idiosyncrasies?
Even with today's impossibly fast computers, LibreOffice can lag for a minute
or two as you correct a false positive by clicking "Ignore All" in a large
document. This office suit is a million times more complex then our tiny shell
script (literally), but is it a million times better?
All of these solutions are unsatisfactory, but that's life in a nutshell. The
English language being what it is, an intelligent spellchecker is science
fiction tantamount to strong AI. Our exercise might teach us some additional
life lessons too: 1) Simple solutions are good enough, 2) Computers cannot fix
the human condition, 3) The life of a writer is tedium. If you want to take a
stab at writing a better spellchecker though, I recommend ch. 13 in Programming
Pearls (Bentley) and ch. 12 in Classic Shell Scripting (Robbins & Beebe).
PIM
[memo_book]
"PIM" is just a fancy acronym for getting organized. My former work place was a
disorganized disaster zone with half a dozen "professional" project management
solutions in place. Every so often my colleagues would be frustrated enough
with the mess that they introduced a new project management tool, which
naturally aggravated the situation further. The moral? Software cannot
magically clean up your mess, only you can organize yourself.
Plan 9 does not pretend to be your nanny, but it does give you basic tools that
you can use to get yourself organized. Such tools include date and cal to keep
track of time, calendar and tel to keep track of appointments and contacts, and
cron to schedule execution of programs (it requires a CPU+AUTH server). And
with just a little bit of awk it's easy to create your own PIM tools. We will
take a look at a few examples here. The following scripts are intentionally
basic, likely they will not suit your needs exactly, but hopefully they can
inspire you to write tools that will!
And whatever operating system you use as your daily driver, keep a physical
notebook by your side - no really!
2do Lists
First off, lets create a simple 2do list manager:
!/bin/rc
2do - simple 2do list manager
usage: 2do [list [ id... | task... ]]
bugs: a task cannot begin with a number
set some defaults
rfork ne
dir=$home/lib/2do
mkdir -p $dir
tmp=/tmp/2do-$pid
date=`{date -i}
parse arguments
if (~ $#* 0) ls -p $dir && exit
if (~ $#* 1){ grep -v '^#' $dir/$1 | sort -k 2; exit }
list=$1 ; shift
id=1 # id is either 1 or one more then the highest id
if (test -f $dir/$list)
id=`{awk '{ if($1 > id) id=$1 } END { print id+1 }' $dir/$list}
id: remove tasks; task: add it
if (echo $* | grep -s '^[0-9\ ]+$'){
for (id in $*)
sed '/^'$id' /s/^/#/' $dir/$list > $tmp && mv $tmp $dir/$list
if not echo $id $date $* >> $dir/$list
And here is a short demonstration of its usage:
; for (thing in eggs milk cheese) 2do buy $thing
; 2do work start some project
; 2do
buy
work
; 2do buy
3 2021-03-23 cheese
1 2021-03-23 eggs
2 2021-03-23 milk
; 2do buy 1 3
; 2do buy
2 2021-03-23 milk
As you can see, this 2do script is very basic. It lets you define an arbitrary
number of lists that you can add tasks to, one at a time, and remove tasks by
listing their ID numbers. Each new task is given a unique ID and today's date,
and the tasks will be listed from oldest to newest. To remove a list completely
just run rm $home/lib/2do/mylist, and you can of course edit the 2do list
manually in a text editor if you wish, eg B $home/lib/2do/mylist.
The script can easily be expanded in many interesting ways, for example you
might want to add priorities and sort by priority first, then by date. The
tasks are not actually removed, but commented out, so it is possible to check
how many tasks have been completed since the project began and give an ETA of
when the list will be completed. Finally, you may want to add flags that let
you adjust some of the defaults here, such as setting a date other then today.
Feel free to experiment and play with the code, and if you have added all of
these features and more, take a step back and consider the difference between
your version and the original. Was it worth the extra complexity?
Queues
Our next script is embarrassingly simple, it's just a crude mechanism for
managing a queue, by printing the next line in a file whenever we run que on
it. But as we shall see, it turns out to be surprisingly useful.
!/bin/rc
que - a simple queue tracker
usage: que [-p] file
set some defaults
rfork ne
tmp=/tmp/que-$pid
pronly=no
check arguments and errors
if (~ $#* 0 || test $#* -gt 2) {
echo Usage: que [-p] file >[1=2]
exit usage
}
if (~ $1 -p) {
pronly=yes
file=$2
}
if not file=$1
if (! test -f $file){
echo Error: File $file does not exist! >[1=2]
exit nofile
}
print task and update queue
if (! task=`{grep -n '<--' $file | sed 's/:.*//'}) task=1
next=`{ echo $task + 1 | hoc }
prev=`{ echo $task - 1 | hoc }
if (~ $pronly yes) { sed -n '$prev'p $file; exit }
sed 's/<--//' $file > $tmp
sed -n ''$task'p' $tmp
sed ''$next's/$/<--/' $tmp > $file
Suppose we are listening through a Red Dwarf audio book, and we have written a
list of these chapters in $home/lib/que/reddwarf, that look like this:
/usr/glenda/music/reddwarf/ch1.mp3
/usr/glenda/music/reddwarf/ch2.mp3
/usr/glenda/music/reddwarf/ch3.mp3
...
If we run que $home/lib/que/reddwarf, it will print /usr/glenda/music/reddwarf/
ch1.mp3, and our list will now look like this:
/usr/glenda/music/reddwarf/ch1.mp3
/usr/glenda/music/reddwarf/ch2.mp3<--
/usr/glenda/music/reddwarf/ch3.mp3
...
The next time we run our command, que will print ch2.mp3 and move the arrow
marker to ch3.mp3. It's easy to automate things further. For example:
; fn reddwarf{ play `{que $home/lib/que/reddwarf} }
; reddwarf # listen to next chapter in our audiobook
; du -a My_Little_Pony | awk '/mp4/ { print $2 }' | sort > $home/lib/que/mlp
; fn mlp{ treason `{que $home/lib/que/mlp} }
; mlp # watch next episode of My Little Pony
At times we may want to print our current task in the queue without advancing
the marker. For example, I regularly attend weekly meetings and keep a list of
meeting notes which look like this:
/usr/dan/jw/litt/work/2022/mwb_E_202209_files/OEBPS/202022327.xhtml
/usr/dan/jw/litt/work/2022/mwb_E_202209_files/OEBPS/202022330.xhtml<--
/usr/dan/jw/litt/work/2022/mwb_E_202209_files/OEBPS/202022332.xhtml
/usr/dan/jw/litt/work/2022/mwb_E_202209_files/OEBPS/202022334.xhtml
...
The notes are provided by an Epub that spans several weeks (one for each line).
I have a simple script that extracts the Epub and update my list, which I need
to run maybe three or four times a year. At the start of each week que is run
automatically on this list to advance the marker. Finally I have a meeting
script that runs que -p $home/lib/meeting and open the corresponding HTML notes
in mothra. I may need to run meeting several times a week, but with this setup
it will always refer to the notes for the current week.
Of course the details of this example will likely not be relevant for you, but
hopefully it can give you some ideas on how to automate your own workflow. The
weekly notes can easily be daily or monthly notes, and they do not need to be a
file. It could be a directory of files or a script to run or what have you
(check out the plumbing section for further tips).
Password Management
All authentication services in Plan 9 are handled by a process called factotum
(a "factotum" is a servant entrusted will the authority to run the masters
estate on his behalf). The idea is somewhat analogous to PAM in UNIX, but much
simpler - yet more powerful. No program in Plan 9, including the kernel,
contain any authentication code whatsoever, it's all centralized in factotum.
This process should already be running, but if not you can start it with auth/
factotum -n. And you should add this to your $home/lib/profile, so that it
automatically runs at every boot. The -n flag here means, "don't look for a
secstore". (more on that later) You can have more then one instance of factotum
running, just as you can have multiple instance of plumber, in case you need to
isolate some authentication service from the rest of the system.
Management of the authentication service is quite easy. To illustrate: when
logging into a UNIX machine with ssh for the first time, factotum will notice
that it doesn't have the needed key, and it will duly prompt you for it, and
save the key safely. Subsequent ssh commands will not ask for a password, since
the authentication service already knows what it is (the keys will be lost
after a reboot though, but keep reading). You can see what keys the factotum
has stored by running cat /mnt/factotum/ctl, it may return something like this:
key proto=pass server=unixpc service=ssh thumb=5+dUiv4yKNhWR3e+DmVu9wvgX
tu5gN3xPgApEWJGMR user=glenda !password?
You will notice that secret information, such as your password, will never be
printed out in plain text. Now we could have added this key manually to
factotum like so:
; echo 'key proto=pass server=unixpc service=ssh thumb=5+dUiv4yKNhWR3e+D
mVu9wvgXtu5gN3xPgApEWJGMR user=glenda !password='my secret password''
> /mnt/factotum/ctl
to delete it, do it manually or with delkey(1)
; echo 'delkey service=ssh' > /mnt/factotum/ctl
; delkey ssh | rc
The real beauty of this service comes into play however, once you couple it
with another service, ei. secstore. Plan 9's secure store saves files in non
volatile RAM using strong encryption, and thus persist safely across reboots.
You need to set up a CPU+AUTH server to use this service, the details on how to
do this can be found in section 7 (7.4.3 for secstored specifically) of the
9front fqa. Once a secstore is running, we can write our factotum key database
and add it to the vault:
; ramfs -p; cd /tmp # write our file to RAM, not to disk
; cat /mnt/factotum/ctl > factotum
; sam factotum # fill in the passwords
; cat factotum
key proto=pass server=unixpc service=ssh thumb=5+dUiv4yKNhWR3e+DmVu9wvgX
tu5gN3xPgApEWJGMR user=glenda !password='my secret password'
key proto=dp9ik dom=mydomain user=glenda !password='don''t forget me!'
; auth/secstore -p factotum
You'll notice that we added two keys here, one for ssh and a Plan 9 user
account (the dom value here is equivalent to authdom in /lib/ndb/local). We can
now change auth/factotum -n in our $home/lib/profile to auth/factotum. During
boot, factotum will now open up the secure store and read any keys it finds in
the encrypted factotum file. To later edit this file, just type ipso factotum.
You can read more about how Plan 9 security works with page /sys/doc/auth.ps,
but let's talk a little bit more about secstore before we call it quits. The
secure store can be used to encrypt any files we want, not just the factotum
database. Suppose we use gpg to manage a list of encrypted passwords in UNIX,
and for convenience keep it around on our Plan 9 box as well. It might look
something like this:
CATEG NAME USER PASSWORD EMAIL WEBSITE
Bank PayPal - 123456 myuser@gmail.com paypal.com
Bank MyBank 123456 password myuser@gmail.com mybank.no
Email GMail myuser MySecret myuser@gmail.com gmail.com
...
We can then do the following:
put our custom database in the secret store
; auth/secstore -p passwords
search the database for passwords
; auth/secstore -G passwords | grep -i bank
; auth/secstore -G passwords | awk '/Bank/ { print $2, $4 }'
securely edit our database
; ipso -e passwords
We can also safely export/import our secret database to a UNIX machine:
export to unix
; ssh unixpc 'gpg2 --gen-key'
; auth/secstore -G passwords | ssh unixpc 'cat | gpg2 -ser myuser > pass.gpg'
import from unix
; ramfs -p; cd /tmp
; ssh unixpc 'gpg2 -d pass.gpg' > passwords
; auth/secstore -p passwords
If you need to constantly import and export such files, you can easily wrap
some of these commands into more user friendly shortcuts. But suppose we don't
have a CPU+AUTH server with a secstore service, can we still manage our
passwords safely? Sure:
; ramfs -p; cd /tmp
; B passwords
; auth/aescbc -e < passwords > $home/lib/pass.aes
and to double check that the password we typed was correct:
; auth/aescbc -d < $home/lib/pass.aes > /dev/null
search the encrypted file for a password
; auth/aescbc -d < $home/lib/pass.aes | grep -i bank
What if we have written something super secret to disk, is there any way to
safely delete the contents? That depends. If a copy of the file exists in the
read only dump file system, then no. A reinstallation of the operating system
is the only way to remove the file. But if that isn't the case, it's simple
enough to overwrite the contents with blank data:
ps: the whitespace in the sed command here is a tab
; dd -if /dev/zero -of myfile -bs 1024 -count `{du myfile | sed 's/ .*//'}
PS: This is a joke of course, there is no way to guarantee that data written to
a modern harddisk is ever removed, no matter what the disk may claim to your
operating system.
Personal Accounting
For many people the word "accounting" sends cold shivers down their spine, and
to be sure, official business accounting tends to be horrifically complex. But
this is largely due to convoluted legislature, and unnecessarily paranoid
triple checking of the math. For personal accounting we don't need to worry
about all that. We just need a way to quickly record our expenses, and a way to
check those expenses against a budget. Here is a simple script that takes care
of our first task:
!/bin/rc
account - add records to our personal account
usage: account [-d date] [-c catg] $$.CC [ comments... ]
set some defaults
rfork e
account=$home/lib/account
date=`{date -i}
catg=food
check arguments and errors
if (~ $#* 0) {
echo 'Usage: account [-c catg] $$.CC [ comments... ]'
exit usage
}
for(arg in $*){
switch($arg){
case -c
catg=$2
shift 2
case -d
date=$2
shift 2
}
}
if (echo $date | grep -vs '^[12][09][0-9][0-9]-[01][0-9]-[0-3][0-9]$') {
echo Error: invalid date, use YYYY-MM-DD >[1=2]
exit wrongdate
}
if (echo $1 | grep -vs '^[0-9.]+$') {
echo Error: invalid expense, use $$.CC without prefixes >[1=2]
exit wrongnumber
}
add record to account
if (~ $catg income) amount=$1
if not amount=-$1
shift
echo $date $amount $catg $* >> $account
And here is a demonstration of its use:
; account -d 2021-03-01 -c rent 1000 it sucks to pay rent
; account -d 2021-03-02 -c income 3500 payday!
this is too much typing, lets reduce it a bit
; fn prompt{ while (echo -n '> ') eval $* `{read} }
; prompt account
-d 2021-03-04 21.25
-d 2021-03-06 14.50 groceries
-c transport 2.50 buss
-c other 9.50 cinema
11.35 # hit Del key to quit input loop
; date -i
2021-03-09
; cat $home/lib/account
2021-03-01 -1000 rent it sucks to pay rent
2021-03-02 3500 income payday
2021-03-04 -21.25 food
2021-03-06 -14.50 food groceries
2021-03-09 -2.50 transport buss
2021-03-09 -9.50 other cinema
2021-03-09 -11.35 food
This demonstration illustrates that personal accounting is often quite tedious.
At least our script tries to reduce some of the workload. If we make the habit
of typing in our daily expenses, we do not have to specify a date. Assuming
that most of our expenses are "food" related, we usually don't need to specify
a category either. The script allows us to give a comment to each input record,
but that is optional. Note that we don't use + or - in our records, the script
will interpret anything with a category of "income" as +, anything else as -.
Lastly, our script requires us to type in one record at a time, but it feels
redundant to type account every time. So we created a small function called
prompt that lets us define a command, account in this case. It reads our input
a line at a time, re-evaluates our line as arguments for our command, and
executes it (somewhat reminiscent of xargs in UNIX, but with an added loop). We
quit the loop by typing the Delete key. I find this trick handy in many
different situations, for example, I might want to look up a bunch of words
while writing an article, prompt look or prompt dict does the trick nicely.
If we plan on using this database for computations, such as summarizing our
monthly expenses and checking it against a budget, it is vital that our
database contain valid data. So we make a couple of extra sanity checks to see
if the provided date and expense are correct. Our checks are not 100% bullet
proof, but it should be good enough for personal use. So for the next step, the
following script checks our current monthly expenses against a predefined
budget:
!/bin/rc
budget - measure monthly expenses against a budget
usage: budget [YYYY-MM]
set some defaults
rfork e
account=$home/lib/account_simple
if (~ $#* 0) date=`{date -i | sed 's/...$//'}
if not date=$1
echo $date
awk '
BEGIN {
printf("%-s\n", "-----------------------------")
}
/'$date'.* income/ { income+=$2 }
/'$date'.* rent/ { rent+=$2 }
/'$date'.* save/ { save+=$2 }
/'$date'.* food/ { food+=$2 }
/'$date'.*/ { sum+=$2 }
END {
printf("%-10s%10.2f %-10s\n", "income:", income, "of 3500")
printf("%-10s%10.2f %-10s\n", "rent:", rent, "of -1000")
printf("%-10s%10.2f %-10s\n", "save:", save, "of -200")
printf("%-10s%10.2f %-10s\n", "food:", food, "of -1000")
printf("%-10s%10.2f %-10s\n", "other:",
sum - (income + (rent + save + food)), "of -1000")
printf("%-s\n", "-----------------------------")
printf("%-10s%10.2f\n", "Balance:", sum)
}' $account
Running the budget command will result in this output:
2021-03
-----------------------------
income: 3500.00 of 3500
rent: -1000.00 of -1000
save: 0.00 of -200
food: -47.10 of -1000
other: -12.00 of -1000
-----------------------------
Balance: 2440.90
Naturally our budget here is unrealistically simple, but it does perhaps
illustrate that accounting, at least for personal expenses, does not have to be
very difficult. If you are more into spreadsheets and the like, take a look at
the spreadsheets section below, for an alternative approach to managing your
finances.
Time Management
There are many elaborate schemes and theories out there for time and project
management. I will not cover that here, instead I will just look at the very
basic tools you'll need for personal time management. First of all the classic
calendar program is well suited to manage your appointments. If the date
happens to be the 24 of March, and you have a $home/lib/calendar file that
looks like this:
Mar 23 Finish the Plan 9 Desktop Guide already!
Mar 24 Flee the country
Mar 25 Dentist appointment
Mar 26 Go home
Running the command calendar will print the following lines:
Mar 24 Flee the country
Mar 25 Dentist appointment
Calendar will print any appointments matching today's and tomorrow's date, or
on a Friday, all dates up until the following Monday. The date and the
appointment have to be separated by a tab. The trick to making this program
useful, besides actually writing down your appointments that is, is to
configure your system to automatically run the program every day. Exactly how
you want to do this depends greatly on your own setup and tastes, but one
simple solution is to add the following to $home/bin/rc/riostart:
window rc -c 'calendar; rc'
If you need a stopwatch, timer or alarm clock, the following examples may
provide you with some hints:
hit enter to stop the clock, "r" is time in seconds
; time read
set timer for 2 minutes
; sleep 120; play $home/music/sample/beep.mp3
set of alarm at 17:10 o'clock
; while(! ~ `{date | awk '{ print $4 }'} 17:10*)
sleep 60; play $home/music/alarm.mp3
Math, Graphs and Units
There are three calculators available in Plan 9: bc, hoc and dc.* All of these
have more or less the same capabilities, and the old UNIX warhorse bc is
probably the one you will be most familiar with (run bc -l to use floating
point math).
The units command is helpful for converting different units, such as meter to
feet or kilogram to pound (it has some limitations though). As for graphs, one
option is to use graph. Suppose you have the following stock exchange printout:
98
99
102 "102"
100
97 "97"
The command graph -y 80 120 -a < stocks | plot will draw a graph, with the y
axis set to vary between 80 and 120, and the x axis set to increment
automatically. The lowest and highest points in the graph are also labeled with
"97" and "102". Of course you can make much more complicated graphs, suppose
you had three columns of numbers in the database, one for each company you have
invested in (each optionally tagged with a label). You can then run the command
graph -y 80 120 -a -o 3 -p rgb < stocks | plot, to produce a graph of the three
companies each with its own color (red, green and blue).
[graph]
Another alternative is gview, eg. awk '{ print NR, $1 }' stocks | gview (where
stocks is the stock exchange example above). It will allow you to do some
interactive operations with the graph, such as changing colors and zooming in.
To show the "complicated" example with three companies, the input file must
list the first company (eg 1 98... 5 97) optionally followed by a color (eg Red
), then the second company (eg 1 88... 5 79), and so on. With all its
capabilities, the graph and gview programs have a fatal flaw: It's clumsy to
incorporate their graphs into documents. A more elegant graph tool for troff
documents is grap. It has much the same capabilities but uses a slightly
different syntax. To add the stock exchange graph from above in a troff
document you could write it as follows:
.G1
98
99
102
100
97
.G2
And you could view the graph by running the command grap stock.ms | pic | troff
| page. Of course if you have a graph of three companies, each with its own
style and label, things would become more complicated. Supposing the plot data
is in a file called stocks, and looks like this:
1 98 67 88
2 99 69 84
3 102 76 81
4 100 82 77
5 97 84 78
You could write the grap graph like so:
.G1
frame invis ht 2 wid 4 left solid bot solid
label left "CompA" left .5 up .7
label left "CompC" left .5
label left "CompB" left .5 down .7
draw compa solid
draw compb dotted
draw compc dashed
copy "stocks" thru X
next compa at $1,$2
next compb at $1,$3
next compc at $1,$4
X
.G2
[grap]
Like the other troff preprocessors, such as tbl, pic and eqn, it takes a bit of
effort to learn the mini-language. But once you get used to the semantics, it's
easy enough to add fairly advanced tables, pictographics, math expressions and
graphs to your troff documents.
Spreadsheets
You do not have a nice pointy-clicky GUI spreadsheet in Plan 9, but it's not
too hard to replicate the basic functionality. Let's assume you have a habit of
doing your personal accounting in LibreOffice, and a typical fiscal year looks
something like the following screenshot:
[localc]
The crucial step in replicating such a report in Plan 9 is to separate data
from presentation. For instance, lets write the variable account data in a
fixed field database like so:
Groceries 345 353 321 398 373 362
Health 134 0 0 123 0 142
Transport 262 268 273 352 263 272
Cloths 0 150 0 0 175 225
Other 363 473 481 403 428 393
With this database in place it's fairly easy to generate the above spreadsheet.
For example, the following awk script will print an ASCII report similar to our
LibreOffice screenshot:
; cat account
!/bin/rc
account - print an account report
usage: account database
bugs: requires a very specific input file
date=`{date}
awk <$1 '
BEGIN {
# set some fixed income/expense values
income=3000; rent=1000; lone=250; savings=500; fixed=1750
# print header and fixed monthly values
printf("%10s%6s%6s%6s%6s%6s%6s\n",
"", "Jan", "Feb", "Mar", "Apr", "May", "Jun")
prfixed("Income", income)
print ""
prfixed("Rent", rent)
prfixed("Lone", lone)
prfixed("Savings", savings)
prfixed("FIXED", fixed)
print ""
}
{
# print each line in the db and save their values
prline($1, $2, $3, $4, $5, $6, $7)
jan+=$2; feb+=$3; mar+=$4; apr+=$5; may+=$6; jun+=$7
}
END {
# print summary of expenses
prline("VARIABLE", jan, feb, mar, apr, may, jun)
print ""
prline("Expenses", jan+fixed, feb+fixed, mar+fixed,
apr+fixed, may+fixed, jun+fixed)
prline("BALANCE",
income-fixed-jan, income-fixed-feb, income-fixed-mar,
income-fixed-apr, income-fixed-may, income-fixed-jun)
print ""
# print current year and annual balance
split("'$"date'", date)
printf("%10s %d\n", "Year", date[6])
printf("%10s %d\n", "SUM",
(income*6)-((fixed*6)+jan+feb+mar+apr+may+jun))
}
a couple of wrapper functions for printf
function prline(tag, jan, feb, mar, apr, may, jun){
printf("%-10s%6d%6d%6d%6d%6d%6d\n",
tag, jan, feb, mar, apr, may, jun)
}
function prfixed(tag, n){
printf("%-10s", tag)
for (i=1; i<=6; i=i+1)
printf("%6d", n)
printf("\n")
}
'
; account database
Jan Feb Mar Apr May Jun
Income 3000 3000 3000 3000 3000 3000
Rent 1000 1000 1000 1000 1000 1000
Lone 250 250 250 250 250 250
Savings 500 500 500 500 500 500
FIXED 1750 1750 1750 1750 1750 1750
Groceries 345 353 321 398 373 362
Health 134 0 0 123 0 142
Transport 262 268 273 352 263 272
Cloths 0 150 0 0 175 225
Other 363 473 481 403 428 393
VARIABLE 1104 1244 1075 1276 1239 1394
Expenses 2854 2994 2825 3026 2989 3144
BALANCE 146 6 175 -26 11 -144
Year 2021
SUM 168
If you are unfamiliar with awk, I am sure the above example looks quite
terrifying. Settle down, brew a cup of coffee, and read the script slowly, line
by line. The logic is fairly straight forward, and most of the tedium here has
to do with formatting. For example printf("%-10s%6d%6d%6d%6d%6d%6d\n"...)
doesn't look pretty, but it makes sure that the fields are printed out nicely
(print a line consisting of a 10 character wide string, followed by six 6
character wide digits followed by a newline).
Now it's all well and good to print ASCII tables for our own personal
accounting, but lets assume we need to incorporate such a report in a business
document. ASCII tables went out of fashion in the early 90's, so we definitely
need something more professional to show to our boss. Don't panic, tbl(1) has
your back! Consider the following example:
; cat << eof > table
.TS
expand center allbox;
l l l l l l l
l n n n n n n.
eof
; account database | sed 's/[ ]+/ /g' >> table
; echo .TE >> table
; tbl table | troff | page
[table]
Let's take a step back and explain what is going on here. The tbl(1) program
expects tab separated input fields, so we use sed to convert our spaces to
tabs. Beyond that our tbl table must start with ".TS" and end with ".TE", and
we need a short header that describes what our table should look like. expand,
center and allbox control various visual aspects of the table, the next two
lines state that the first row consists of seven left justified text fields,
and that all following rows after that consist of a left justified text field
and six numerical fields. Look up the tbl(1) manpage for more information, you
can do a lot of cool stuff with it. To incorporate our table in an ms (ei.
troff) document, just run cat table >> document.ms.
At first glance, our examples may look tedious, but they are actually not much
harder to work with then our LibreOffice example. The above spreadsheet in
LibreOffice consists of 550 characters. Some of these fields contain code, for
example a field that reads "1104", may actually be typed "=SUM(B12:B17)".
(compare that to awk's "jan+=$2") In addition to typing in these characters, we
also need to use at least 101 mouse or keyboard actions to manipulate the
table, making a total of 651+ actions.
Our awk program is 982 characters, excluding comments and whitespace, and our
database 118, making a total of 1100 input actions. So our awk table requires
initially 50% more work to write then our LibreOffice table. However, once our
awk program is written, we only need to update the database when we do our
accounting, and that is five times less work then our LibreOffice spreadsheet.
In addition we can freely change our awk code without effecting the data, we
can also use our data with other programs, we can feed it to graph or a
database for instance. The flexibility of our awk approach, not to mention
computational efficiency, is far superior! Proactive laziness is understandably
scary for the novice, but with experience one tends to embrace its wisdom.
But lets consider one more problem: Writing a custom tbl file just to quickly
view our data as a troff table is indeed tedious, can we automate this? Sure.
Lets write a script called table that automatically writes a tbl table for the
file it is given and open it in page:
!/bin/rc
table - convert database to a tbl(1) spreadsheet
usage: table file
bugs: only supports a simple generic spreadsheet
set some defaults
rfork ne
tmp=/tmp/ttbl-$pid
mkdir -p $tmp
fn sigexit{ rm -rf $tmp }
workaround: tbl can only handle one page (56 lines) at a time
pages=`{echo `{cat $1 | wc -l} / 56 | hoc}
if(~ $pages [0-9]*.[0-9]*){
pages=`{echo $pages | sed 's/\.*//'}
pages=`{echo $pages + 1 | hoc}
}
s=1
e=56
for(p in `{seq $pages}){
p=`{echo 00$p | sed 's/.*(...$)/\1/'}
sed -n $s,$e^p $1 > $tmp/p$p
s=`{echo $s + 56 | hoc}
e=`{echo $e + 56 | hoc}
}
generate tbl for each 56 line segment
for(file in $tmp/p*){
tbl=$file.tbl
echo .TS > $tbl
echo 'expand center allbox;' >> $tbl
# create tbl header (header and content lines)
for(word in `{sed 1q $1 | sed 's/[ ]+/_/g'}){
if (echo $word | grep -s '^[0-9.-]+$') echo -n 'n '
if not echo -n 'l '
} >> $tbl
echo >> $tbl
for (word in `{sed -n 2p $1 | sed 's/[ ]+/_/g'}){
if (echo $word | grep -s '^[0-9.-]+$') echo -n 'n '
if not echo -n 'l '
} >> $tbl
echo . >> $tbl
cat $file >> $tbl
echo .TE >> $tbl
}
compile all segments and print out
for (file in $tmp/p*.tbl){ tbl $file | troff >> $tmp/all }
page $tmp/all
exit # force clean up
One complication here is that tbl does not handle tables that overflow the
page, so we need to split very large tables into smaller chunks. And of course
our script cannot magically produce a perfect table for any and all input.
First of all it just scans the first two lines to find out what type of fields
it should print, left justified text or numbers, it assumes that all following
lines have the same fields as the 2nd line. Lastly our input file must be a tab
separated database, if it isn't we need to transform it first (eg. sed 's/,/ /
g' db.csv > db.tab; table db.tab).
Databases
[these_mark]
"Database" is another one of those IT buzzwords, that make really simple things
sound amazingly complex. Consider this text file:
Asia Japan 120 144
Asia India 746 1267
Asia China 1032 3705
Asia USSR 275 8649
Europe Germany 61 96
Europe England 56 94
Europe France 55 211
North America Mexico 78 762
North America USA 237 3615
North America Canada 25 3852
South America Brazil 134 3286
Lo, and behold, it's a database! A database is a list of values, nothing more.
The above table is a database of countries, listing continent, name, population
and area. We can easily retrieve values from our database with awk, for
instance:
; echo Asias population is `{awk -F' ' '
/Asia/ { sum += $3 } END { print sum }' countries}
Asias population is 2173
; echo Germanys population density is `{awk -F' ' '
/Germany/ { print ($3*1000)/$4 }' countries}
Germanys population density is 635.417
Naturally these numbers are quite bogus, since my database is incomplete, and a
bit outdated, but I trust you get the point. Of course when people speak of
databases, they often think of relational databases. That is tables of values
that are related with each other through common key values. For example,
suppose we augment our countries database with an up to date capital database:
Brazil Brasilia
Canada Ottawa
China Beijing
England London
France Paris
Germany Bonn
India New Delhi
Japan Tokyo
Mexico Mexico City
USA Washington
USSR Moscow
These two databases are related with each other through the common country
names, the second field in our countries database, and the first in our
capitals database. Lets merge them:
; sort -t' ' -k 2 countries > tmp_countries
; sort -t' ' capitals > tmp_capitals
; join -t' ' -1 2 tmp_countries tmp_capitals
Brazil South America 134 3286 Brasilia
Canada North America 25 3852 Ottawa
China Asia 1032 3705 Beijing
England Europe 56 94 London
France Europe 55 211 Paris
Germany Europe 61 96 Bonn
India Asia 746 1267 New Delhi
Japan Asia 120 144 Tokyo
Mexico North America 78 762 Mexico City
USA North America 237 3615 Washington
USSR Asia 275 8649 Moscow
You will note one complication here. Our sort and join commands have the flag
-t' ' (-F' ' for awk), that is -t followed by a Tab character surrounded by
single quotes. This is because our databases are tab separated values, this
allows us to have fields containing spaces, such as "North America". Without
the -t' ' flag, this would be interpreted as two fields rather then one. Of
course we can use the same approach to work with comma separated values, just
change the flag to -t,.
If you try this out yourself, you will see that we have actually cheated a bit
in our examples. Tab separated databases do not align perfectly, they actually
look more like this:
Asia Japan 120 144
Asia India 746 1267
Asia China 1032 3705
Asia USSR 275 8649
Europe Germany 61 96
Europe England 56 94
Europe France 55 211
North America Mexico 78 762
North America USA 237 3615
North America Canada 25 3852
South America Brazil 134 3286
In UNIX it is easy to pretty print such text, just run join -t' ' -1 2
tmp_countries tmp_capitals | column -t. Plan 9 however does not have the column
command. The closest equivalent, mc, does not have this auto align feature. But
it's not too hard to write an awk script that does the same, here is one
example:
PS: This script will replace tabs, so don't overwrite your tab separated
databases with it! Use it for pretty printing only.
!/bin/rc
column - auto align column output
usage: column < input > output
cat /fd/0 | awk '
BEGIN {
FS = "\t"; blanks = sprintf("%100s", " ")
number = "^[+-]?([0-9\ ]+[.]?[0-9\ ]*|[.][0-9\ ]+)$"
}
{ row[NR] = $0
for (i = 1; i <= NF; i++){
if ($i ~ number)
nwid[i] = max(nwid[i], length($i))
wid[i] = max(wid[i], length($i))
}
}
END {
for (r = 1; r <= NR; r++){
n = split(row[r], d)
for (i = 1; i <= n; i++){
sep = (i < n) ? " " : "\n"
if (d[i] ~ number)
printf("%" wid[i] "s%s", numjust(i, d[i]), sep)
else
printf("%-" wid[i] "s%s", d[i], sep)
}
}
}
function max(x, y){ return (x > y) ? x : y }
function numjust(n, s) { # position s in field n
return s substr(blanks, 1, int((wid[n]-nwid[n])/2))
}'
Awk as a Database
OK, so we can merge our relational databases, but this is still a lot of work.
Can we automate this process? And besides, it's not so intuitive to write awk '
/Germany/ { print ($3*1000)/$4 }', could we possible write awk '$country ==
"Germany" { print ($population*1000)/$area }'? Yes. The following script allows
awk to query a relational database. It only requires you to write a relfile
first, that describe what attributes are where. The relfile must also contain a
table with all available attributes. If such a file does not exist, it must be
created, and the instructions for doing so must be provided in the relfile.
; cat relfile
countries:
continent
country
population
area
capitals:
country
capital
cc:
country
population
area
capital
continent
!sort -t' ' -k 2 countries > tmp_countries
!sort -t' ' capitals > tmp_capitals
!join -t' ' -1 2 tmp_countries tmp_capitals > cc
!rm tmp_* cc
; cat q
!/bin/rc
q - awk relational database query
usage: q query
depend: relfile
echo $* | awk '
BEGIN { readrel("relfile") }
/./ { doquery($0) }
parse relfile
function readrel(f) {
while (getline <f > 0 )
if ($0 ~ /^[A-Za-z]+ *:/) {
gsub(/[^A-Za-z]+/, "", $0)
relname[++nrel] = $0
} else if ($0 ~ /^[ \t]*!/)
cmd[nrel, ++ncmd[nrel]] = substr($0,index($0,"!")+1)
else if ($0 ~ /^[ \t]*[A-Za-z]+[ \t]*$/) # attribute
attr[nrel, $1] = ++nattr[nrel]
else if ($0 !~ /^[ \t]*$/)
print "bad line in relfile:", $0
}
translate qawk query into corresponding awk query
function doquery(s, i,j) {
for (i in qattr)
delete qattr[i]
query = s
while (match(s, /\$[A-Za-z]+/)) {
qattr[substr(s, RSTART+1, RLENGTH-1)] = 1
s = substr(s, RSTART+RLENGTH+1)
}
for (i = 1; i <= nrel && !subset(qattr, attr, i); )
i++
if (i > nrel)
missing(qattr)
else {
for (j in qattr)
gsub("\\$" j, "$" attr[i,j], query)
for (j = 1; j <= ncmd[i]; j++) # create table i
if (system(cmd[i, j]) != 0) {
print "command failed, query skipped\n", cmd[i,j]
return
}
awkcmd = sprintf("awk -F''\t'' ''%s'' %s", query, relname[i])
#printf("query: %s\n", awkcmd) # for debugging
system(awkcmd)
}
}
function subset(q, a, r, i) { # is q a subset of a[r]?
for (i in q)
if (!((r,i) in a))
return 0
return 1
}
function missing(x, i) {
print "no table contains all of the following attributes:"
for (i in x)
print i
}'
; q '$country == "Germany" { print ($population*1000)/$area }'
635.417
; q '{ printf("%-10s %4.2f\n", $country, ($population*1000)/$area) }'
Japan 833.33
India 588.79
China 278.54
USSR 31.80
Germany 635.42
England 595.74
France 260.66
Mexico 102.36
USA 65.56
Canada 6.49
Brazil 40.78
Our little q command will likely not topple Oracle anytime soon, but for
personal use awk is both flexible and efficient. By the way both this section,
and the above spreadsheet section is greatly [S:plagiarized from:S] influenced
by chapter 4 in The Awk Programming Language, by Aho, Kernighan and Weinberger.
I highly recommend this book for your own personal library, whether you use
Plan 9, UNIX or flippin DOS.
Ndb as a Database
Using awk for databases is fine, but it's a very UNIX'y way of doing things.
Plan 9 actually has a really good, and super fast, database called ndb (network
database). You have probably already used ndb to set up your network
configuration file /lib/ndb/local, and it's for this purpose ndb was created.
But it is actually a fully functional generic database, with all the trimmings.
Here is how we can implement the above country database in ndb:
; cat countries.db
country=Japan
continent=Asia
population=120
area=144
capitol=Tokyo
country=France
continent=Europe
population=55
area=211
capitol=Paris
country=Mexico
continent="North America"
population=78
area=762
capitol="Mexico City"
...
; population=`{ndb/query -f countries.db country Germany population}
; area=`{ndb/query -f countries.db country Germany area}
; echo Germanys population density is `{echo '('$population'*1000)/'$area'' | hoc}
Germanys population density is 635.416666667
; ndb/query -f countries.db continent Asia
country=Japan continent=Asia population=120 area=144
country=India continent=Asia population=746 area=1267
country=China continent=Asia population=1032 area=3705
country=USSR continent=Asia population=275 area=8649
; ndb/query -a -f countries.db continent Asia population |
awk '{ sum+=$1 } END { print "Asias population is", sum }'
Asias population is 2173
ndb entries are very free form, we can write them in one line, as in country=
Japan continent=Asia population=120..., in multiple lines indented by spaces or
tabs, or a combination of both. The empty newline that separate the entries
above is not required, it's just added for the sake of readability. Note the -a
flag in our last ndb example, without this ndb would return 120, the population
of the first entry in continent Asia. ndb can handle multiple database files,
and make attribute hashes to speed things up. See ndb(8) for the full details.
Conclusion
The primary value of Plan 9 lies in its simplicity. Making a hard copy of its
documentation will not break your bookshelf, and the source code is actually
readable. This cannot be said for main stream operating systems. Of course
being such a simple system, there are many many features that popular operating
systems provide, that Plan 9 don't. If you plan on using this alternative OS as
a daily driver, you really have to pull up your sleeves, learn some shell,
maybe even some C, and write a bunch of utilities to do your work. But it's a
fascinating learning experience.
Hopefully, this article has also demonstrated that Plan 9 does not suck quite
so badly as you may have thought. I know I was positively surprised a few times
as I was writing it! Thanks to the good work of the 9front developers, you can
run Plan 9 on modern hardware. Most of the laptops I have tried it on have just
worked, including essential things like audio and wifi. As we have seen, you
can do office work, play games and audio, work with images and the web. And
with the recent additions of a fairly decent music player, video player and web
browser, 9front is actually starting to look, if not good exactly, then not so
shabby at least, as a casual desktop. Of course the real charm of Plan 9 has
always been in its simple and consistent design, which gives the user
tremendous power with modest efforts. Using it will likely open your mind to
the power of UNIX, much more so in fact, then UNIX itself will. Happy hacking
:)
[journeys_e]