Plan9/Sam
Sam is an interactive multi-file text editor. In addition to a mouse-driven cut-and-paste interface, a textual command language makes complex or repetitive editing tasks easy to specify. The command language is characterized by the composition of regular expressions to describe the structure of the text being modified.
A normal sam
session begins with a new sam terminal window. You are
expected to use the console at the top and mouse chording to
manipulate files and edit text. sam
behaves pretty much like ed if
you invoke it with sam -d
.
This post assumes you have read The Sam Paper and are running on 9front.
To switch from a text window to the end of the command window use ctrl+b
.
Ctrl+g
switches to the last focused window. This is useful to return
to a text window after having switched to the command window.
Manipulating Files
To open all source, header, and GN build files located in src
and inc
folders run the following command in a
rio rc
window:
% cd $home/NEIN999/
% sam `{walk -f src inc | grep '\.(c|cpp|cc|h|gn)'}
The above missed to add make files to the list of files
sam
knows about:
B < walk -f src | grep '(Makefile|mkfile)'
Note: the previous command is executed in the ‘samterm’ window.
To see what files are known to sam you can either right click or type
n
into a samterm
window to get a list of files (sam
is a
multi-file editor so it keeps track of all files it was told to open).
Large scale software projects contain hundreds of files organised in
more or less complex file hierarchies. There might be so many files
that selecting a file using the mouse is cumbersome. It might be
quicker to search for files using sam
s command language.
To see all .cpp
files known to sam
that are in a util
folder
one can use the following:
X/- .*\/util\/.*\.cpp/
That will print:
-. src/util/Allocate.cpp
-. src/util/Arguments.cpp
-. src/util/Atomic.cpp
...
To open one of the files prepend the file name with b
, select b src/util/Allocate.cpp
, middle-click and chose send
.
It is easy to get into a frenzy and modifiy a multitude of files. To check what files have been modified the following command is useful:
X/'/
In this particular example it prints:
'+. src/util/Allocate.cpp
Especially after bulk search and replace actions many files might be modified. To save all modified files to disk run the following:
X/'/ w
Commands that output file names usually prepend the name with a few characters. The first three characters are a concise notation for the state of the file:
Encoding | Description |
---|---|
. |
indicates the current file |
+ |
the file has a window open |
- |
the file has been read by sam |
* |
the file is open in more than one instance |
' |
the loaded file differs from the file on disk |
Search and Replace
Search
Let’s check what files have a copyright
notice and print some
context to check the year, company name, and the presence of a
specific tag before for all source files in the util
folder:
X/.*(inc|src)\/util\/.*\.(cc|cpp|h)/ ,g/Copyright \(C\)/ {
,x/.*\n/ g/Copyright \(C\)/ {
f
=
-1,+1 p
}
}
This command executed in the samterm
window produces the following
output:
-. inc/util/Adaptor.h
3; #16,#83
<copyright>
Copyright (C) 2013 PlanNein, Inc. This software and the associate
documentation are confidential and proprietary to PlanNein, Inc.
-. inc/util/Allocate.h
9; #301,#368
<copyright>
Copyright (C) 2013 PlanNein, Inc. This software and the associate
documentation are confidential and proprietary to PlanNein, Inc.
...
Need more context before or after the match, similar to UNIX
style grep -C2 ...
or grep -B2 -A3 ...
?
X/.*(inc|src)\/util\/.*\.(cc|cpp|h)/ ,g/Copyright \(C\)/ {
,x/.*\n/ g/Copyright \(C\)/ {
f
=
-2,+3 p
}
}
Note how the output now shows two lines before the match and three
lines after the matches (i.e. like grep -B2 -A3 ...
:
-. inc/util/Adaptor.h
3; #16,#83
/*
<copyright>
Copyright (C) 2013 PlanNein, Inc. This software and the associate
documentation are confidential and proprietary to PlanNein, Inc.
Your use or disclosure of this software is subject to the terms and
conditions of a written license agreement between you, or your company,
-. inc/util/Allocate.h
9; #301,#368
/*
<copyright>
Copyright (C) 2013 PlanNein, Inc. This software and the associate
documentation are confidential and proprietary to PlanNein, Inc.
Your use or disclosure of this software is subject to the terms and
conditions of a written license agreement between you, or your company,
...
Replace
Replacing stuff in a single file is easy. Doing it across multiple files
is much more interesting. Let’s say we want to replace all occurrences
of Copyright (C)
in source files with COPYRIGHT ©
:
X/\.(cc|cpp|h)/ ,g/Copyright \(C\)/ {
,x/.*\n/ g/Copyright \(C\)/ {
s/Copyright \(C\)/COPYRIGHT ©/
}
}
Let’s break this down:
X/\.(cc|cpp|h)/
… selects all source files ending in.(cc|cpp|h)
thatsam
knows about,g/Copyright \(C\)/
… out of that set only considers files matching the patternCopyright \(C\)
,x/.*\n/
… for each line of such a fileg/Copyright \(C\)/
… that contains the patternCopyright \(C\)
{ s/Copyright \(C\)/COPYRIGHT ©/ }
… substitute the replacement text for the first match, in dot, of the regular expression.
You can do much more advanced things than simple search replace once you get the hang how to use the command language.
Cheatsheet
Addresses
n,m |
line n to line m |
’ |
address mark, see k below |
. |
correct selection/position |
0 |
correct selection/position |
ˆ |
start of file |
$ |
end of line/file |
, |
equivalent to 0,$ |
Regular Expressions
. |
any character |
* |
0 or more of previous |
+ |
1 or more of previous |
[ˆn] |
correct selection/position |
[nm] |
n or m |
(re) |
tag pattern |
# |
substitute #’th tagged pattern |
Text Commands
-/re/ |
search backward |
+/re/ |
search forward |
/re/ |
search in same direction as last |
a/text/ |
Append text after dot |
c/text/ |
Change text in dot |
i/text/ |
Insert text before dot |
d |
Delete text in dot |
s/regexp/text/ |
Substitute text for regexp in dot |
m address |
Move dot to after address |
t address |
Copy dot to after address |
Display Commands
p |
Print contents of dot |
= |
Print value of dot |
n |
Print file menu list |
I/O Commands
n |
Print list of read and loaded files |
b file-list |
Set current file to first in menu list |
B file-list |
As b, but load new file-list |
D file-list |
Delete named buffers |
e [file-name] |
Replace current with file |
r file-name |
Replace dot by contents of file |
w file-name |
Write current to named file |
f [file-name] |
Set/Change current file name |
< command |
Replace dot by stdout of command |
> command |
Send dot to stdin of command |
| command |
Pipe dot through command |
! command |
Run the command |
Loops and Conditionals
x/regexp/ command |
Set dot and run command on each match |
x cmd |
Set dot and run command on each matching line |
y/regexp/ command |
as x but select unmatched text |
X/regexp/ command |
Run command on files whose menu line matches |
Y/regexp/ command |
As X but select unmatched files |
g/regexp/ command |
If dot contains regexp, run command |
v/regexp/ command |
If dot does not contain, run command |
Miscellaneous
k |
Set address mark to value of dot |
q |
Quit |
u n |
Undo last n (default 1) changes |
{ } |
Braces group commands |
<compose> Xnnnn |
Insert char xxxx hex (Unix/Plan9) |
Idioms
X/.*/,x/<cr>/d |
strip |
x/ˆ/ .,/0d |
strip C comments from selection |
-/ˆ/+#10 |
goto the 10th colum in the current line |
-0+,+0- |
round dot down to whole lines only |
,x/ +/ v/ˆ/ c/ / |
compress runs of spaces, leaving indentation |
s/"([ˆ"]*)"/‘‘1’’/ |
replace “hello” with ‘‘hello’’ in selection |
s/.*/*&*/ |
enclose dot with asterisk |
f <nl> |
set current file-name to null |
> echo "" |
insert ascii code xxx at current pos |
, > wc -l |
count lines in file |
/text/+-p |
highlight the lines containing |
-/text/ |
search for text backwards |
$-/text/ |
search for the last occurrence of text in file |
,x/<text>/+-p |
grep for text |
.x/<pat>/ c/<rep>/ |
search for |
B < echo *.c |
add all the C files in current dir to file list |
B < grep -l <pat> * |
add all the files containing |
X/’/w |
write all modified files |
Y/.c/D |
remove all non C files from file list |
| fmt |
pipe selection through the text formatter |
> mail <user> |
send selection as email to |
x/0 a/0 |
double space selection |
x/ˆ/ a/ / |
indent selection 1 tab |
x/ˆ<tab>/d |
remove 1 tab of indent from selection |
/(.+0+/ |
matches blocks of text separated by blank lines |
! date |
get current date in sam window |
,< wc |
push file into wc, count appears in sam window |
0 < date |
insert date at start of file |
1 < date |
replace first line with todays date |
X D |
remove out all up-to-date files |
,| sort |
sort current file |
,x/ˆTODAY$/ < date |
replace TODAY with the output of date |
,x/Plan9/|tr a-z A-Z |
replace all instances of Plan9 with upper case |
,t "junk.c" 0 |
copy current file to start of junk.c |
-/.PP/,/.PP/- |
highlight current paragraph in an nroff doc |