A better shell for UNIX based on Plan 9 rc
  • C 80.6%
  • Roff 10.7%
  • HTML 6.7%
  • Yacc 1.1%
  • Makefile 0.7%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Olivier Duclos d27061fc53
All checks were successful
ci/woodpecker/push/build_gcc/3 Pipeline was successful
ci/woodpecker/push/build_gcc/2 Pipeline was successful
ci/woodpecker/push/build_gcc/1 Pipeline was successful
ci/woodpecker/push/build_alpine Pipeline was successful
Remove the legacy editline support
It was never fully working anyway. Note that this is not the same
editline library that is shipped on BSD systems.
2026-08-20 21:13:41 +02:00
.woodpecker ci: also run test on pull requests 2026-08-19 22:09:51 +02:00
bench Add a new benchmark to measure how fast we count lists 2025-10-14 00:30:50 +02:00
ext Support bestline for line editing 2026-08-20 20:29:21 +02:00
maint maint: add a safety check in mkrelease 2024-04-30 23:15:55 +02:00
museum museum: Add an offline copy of rc hacking notes from tobold.org 2026-07-12 17:18:40 +02:00
test-history Fix treatment of slashes (path separator) by history command 2024-10-06 18:55:46 +02:00
.gitignore update gitignore 2025-10-30 21:52:47 +01:00
.vimrc whitespace cleanup 2018-03-20 21:59:14 +00:00
addon.c Switch to C99 booleans 2023-06-17 22:45:46 +02:00
addon.h Remove all the useless 'extern' in function declarations 2023-10-27 18:56:58 +02:00
AUTHORS Rename rc to rc23 2023-06-10 17:29:07 +02:00
builtins.c bugfix: the function nesting counter was not always being decremented 2026-08-19 21:58:50 +02:00
check_readline.sh build: fix linking of readline on Solaris 2026-07-21 14:40:53 +00:00
config.def.h build: detect the availability of /dev/fd at build time 2024-06-29 23:36:04 +02:00
COPYING Rename rc to rc23 2023-06-10 17:29:07 +02:00
develop.c Fix 3 display errors in tree dump 2026-07-19 17:11:37 +02:00
dot.rcrc Update dot.rcrc 2025-10-31 01:21:20 +01:00
edit-bestline.c Adapt edit-bestline.c to rc23 2026-08-20 21:13:41 +02:00
edit-null.c Suppress all -Wunused-param warnings 2024-05-20 19:20:54 +02:00
edit-readline.c Do not write lines starting with a space or tab to $history 2026-07-16 16:12:00 +02:00
edit.h Do not write lines starting with a space or tab to $history 2026-07-16 16:12:00 +02:00
except.c Avoid printing null error messages 2024-06-11 21:47:07 +02:00
exec.c Remove checks for POSIX.1-1988 features 2024-04-19 12:58:25 +02:00
fn.c Suppress all -Wunused-param warnings 2024-05-20 19:20:54 +02:00
footobar.c Print the filename in error messages in addition to the line number 2025-01-04 20:11:38 +01:00
getopt.c Remove all the useless 'extern' in function declarations 2023-10-27 18:56:58 +02:00
glob.c Do not check for lstat(2) 2024-04-19 18:03:55 +02:00
glom.c Support negative variable subscripts. 2026-08-17 14:41:24 +02:00
hash.c Add prototype "void" for functions without arguments 2023-12-27 18:39:41 +01:00
heredoc.c Avoid a -fanalyzer warning in heredoc parsing 2026-07-13 17:48:55 +02:00
history.1 doc: Fix the .Xi macro in history.1 2023-11-05 22:37:40 +01:00
history.c history: fix UB when calling execl() 2026-07-19 17:11:48 +02:00
input.c Do not write lines starting with a space or tab to $history 2026-07-16 16:12:00 +02:00
input.h Remove all the useless 'extern' in function declarations 2023-10-27 18:56:58 +02:00
lex.c lexer: tiny optimization 2026-01-10 00:47:13 +01:00
list.c Reduce the chances of integer overflow with listnel() 2025-10-14 00:25:44 +02:00
main.c Make $nl and $tab non-exportable by default 2026-08-02 21:57:27 +02:00
Makefile Remove the legacy editline support 2026-08-20 21:13:41 +02:00
match.c Catch end-of-string in the source string when performing a range match 2025-09-22 22:30:09 +02:00
mksignal.c Require C99 and POSIX.1-2001 2023-06-17 21:12:08 +02:00
mkstatval.c Initial revision 2015-05-16 21:23:22 +01:00
nalloc.c Add prototype "void" for functions without arguments 2023-12-27 18:39:41 +01:00
open.c security: make $history only readable by its owner 2024-02-19 00:16:30 +01:00
p9p-test.rc Add test.rc from Plan 9 to our test suite 2023-11-05 23:44:25 +01:00
parse.c parser: rebuild with the latest version of byacc 2026-01-06 22:11:50 +01:00
parse.h Add generated parser files 2023-07-01 12:09:34 +02:00
parse.y Allow for-loops to set multiple variables 2024-06-06 16:13:06 +02:00
print.c Simplify variadic arguments handling 2026-07-14 11:21:39 +02:00
proto.h Automatically check for setrlimit(3) support 2024-04-22 14:15:46 +02:00
rc.h Add a limit to function nesting 2025-11-09 20:50:44 +01:00
rc23.1 Adapt edit-bestline.c to rc23 2026-08-20 21:13:41 +02:00
README.md Remove the legacy editline support 2026-08-20 21:13:41 +02:00
redir.c Add prototype "void" for functions without arguments 2023-12-27 18:39:41 +01:00
rlimit.h On FreeBSD, support 3 new limits: pipebuf, umtxp and vms. 2026-07-23 17:35:31 +02:00
signal.c Remove checks for POSIX.1-1988 features 2024-04-19 12:58:25 +02:00
status.c Remove checks for POSIX.1-1988 features 2024-04-19 12:58:25 +02:00
system.c Remove all the useless 'extern' in function declarations 2023-10-27 18:56:58 +02:00
tree.c Fix a panic when ussing ``{} inside a function defined multiple times 2026-07-12 16:19:20 +02:00
trip.rc bugfix: the function nesting counter was not always being decremented 2026-08-19 21:58:50 +02:00
tripping.c Simplify the Makefile 2025-01-17 22:58:00 +01:00
utils.c Add support for %r to fprintf() 2025-11-09 00:58:34 +01:00
var.c Fix printing of variable 'not' by whatis 2025-10-14 00:25:36 +02:00
VERSION v2.0.7 2026-07-19 17:27:08 +02:00
wait.c Rephrase the error message when builtins get too many arguments 2025-10-30 22:01:21 +01:00
walk.c Fix cmdarg exception leak in various edge cases 2026-05-13 23:51:25 +02:00
which.c Cleanup which.c 2026-07-13 20:27:04 +02:00

; rc23

rc23 is a shell for UNIX and Linux. It is a reimplementation of the Plan 9 rc shell with a few extensions and adaptations, but it tries to be backward compatible. It offers a C-like syntax massively simplified compared to the Bourne shells, which makes it easier to learn, read, and safer to use when writing scripts.

Examples

# One of the main advantage of rc shells compared to bourne shells
# (like dash, bash, ksh, zsh...) is that variables are not strings
# but arrays of strings.
cities = ('San Francisco' 'New York' Berlin 東京都)

# You do not need to quote your variables anymore!
for (city in $cities)
    echo $city

> San Francisco
> New York
> Berlin
> 東京都

# In fact, double-quotes are just a normal characters in rc23. Let's join
# our cities with another array, and print the results with double-quotes:
zones = (America America Europe Asia)
echo "$zones/$cities"

> "America/San Francisco" "America/New York" "Europe/Berlin" "Asia/東京都"

# ~ is the match operator. We use it to compare variables with patterns.
if (~ `pwd $home) {
    echo We are at home!
} else if (!~ $cwd /*) {
    echo warn: '$cwd' is not an absolute path: "$cwd" >[1=2]
    ~ $fatal true && exit 1
}

Differences with Byron's rc

  • functions can have named arguments
fn warn(file line) { echo $file:$line: $* >[1=2] }
  • range support in variable subscripts
echo $var(1 3-6 7 8 9-)
  • list append operations are optimized to avoid unneeded memory allocations, which brings an almost 20x speedup with large arrays
list = ($list x y z)
  • true and false are builtin

  • return and exit accept arbitrary strings as return value, like on Plan 9. But since we are on UNIX, non-empty strings evaluate to 1.

  • path lookups are cached

  • builtin wait accepts multiple PIDs and will set $status accordingly [edit: this has been merged in rc]

true&false&true& wait $apids; whatis status
status=(0 1 0)
  • $ofs can be used to control list flattening
x=(a b c)
ofs=', ' echo $"x
> a, b, c
  • the double-backquote process substitution without $ifs allows to get the whole output in one string, while still trimming the last newline. This is important for cases where the output of a command might contain newlines (and directories on Unix sure can), like me = ``{basename $0}.
x = ``{ls /dir}
whatis x
> x='file1
file2
file3'
  • a simple read builtin is provided (optional)

  • the flag builtin accepts multiple flags at once

  • flag -u can detect undeclared variables and function calls with less arguments than their signature

  • for loops can set multiple variables at a time

for (number file in `{ls | nl})
    echo $number: $file
  • error messages contain the file name where the error occurred

  • the . (dot) builtin looks for files in $path, like in p9rc

  • you can do multiple-assignments at once

(kernel version arch) = `{uname -srm}

# It can also be used to swap variables

(x y) = ($y $x)
  • more changes are planned in the future

Installation

Requirements

  • a C99 compiler
  • make (GNU or BSD)
  • readline, or edit on BSD (optional)

Steps

  1. make
  2. make check to run tests (optional)
  3. make install

To specify the line editing library, add EDIT={readline,edit,bestline,null} to each step. readline is used by default (it has the best completion).

FreeBSD and DragonFly users will probably need to add LDFLAGS=-L/usr/local/lib for the compiler to find the library.

To compile the addon builtin functions (see addon.c), you must pass RC_ADDON=1 to make.

The history program must be built explicitly: make history. It will get installed as -, -p, -- and --p. See history.1.

When installing, you can customize PREFIX and MANPATH. rc23 is installed into /usr/local by default.

If you want to make rc23 your default shell, you need to add it to the list of allowed shells on your system, usually in /etc/shells. Then run chsh(1) to make it your default shell. This is not recommended unless you really know what you are doing.

Run make uninstall to uninstall rc23.

Usage

Read the manpage! man rc23

The easiest way to use rc23 is to configure your terminal emulator program to start rc23 instead of the default shell. In that case, remember to run it as a login shell (-l), or else it will not read $home/.rcrc when starting up.

Take a look at dot.rcrc for an example.

FAQ

Is rc23 compatible with POSIX sh(1)?

It is not. That's the point!

Why should I use rc23 then?

Because, like most things coming from Plan 9, rc is just better designed: the syntax has been simplified while making it more powerful, and much less error-prone. How many times have I seen buggy shell scripts because the author forgot to put double-quotes around its variables, or because of the subtly broken error handling? Clearly, most people do not want to spend a lot of time learning the details of how their shell works. So we might as well use something simpler. Not to mention that many advanced features of the Bourne shells are rarely used (job control, complex parameter expansion, regex support, etc.). By taking away what is not essential, we end up with a more elegant design.

And talking about elegance, rc23's syntax is very close to C, even more so than csh(1). Hasn't it ever bothered you that UNIX, an operating system born with C, has a shell with a pascal-like syntax?

How do I configure rc23?

rc23 reads only one file on startup: ~/.rcrc. You can put your environment variable and functions in there (rc23 does not have aliases, just use functions). You can also customize the prompt by setting the $prompt variable. Unlike in other shells, there is no special syntax for $prompt. To make it dynamic, define a prompt function. It will be executed each time rc23 displays $prompt.

An example is available in dot.rcrc.

Does it have auto-completion?

Yes, all the supported editing libraries offer at least path and command completion, as well as history search (ctrl+r). Only libedit(3) has troubles with file names containing spaces and other special characters. Patches welcome!

Why is it not written in Rust?

Because I have a real job and I do not have the time/energy for a full rewrite. Besides, rc23 aims to be highly portable, including on old platforms where running rust would not be practical.

How do I declare local variables with rc23?

By default, all variables declared in rc23 are exported to the environment. To avoid that, you have 3 solutions:

  • declare a variable attached to a block: x=hello { echo $x }
  • use a function with named arguments: fn greet(x) { echo $x }
  • add your variable to $noexport. This is highly discouraged: $noexport is reserved for rc23's special variables.

Of course, you can also simply undefine variables when you don't need them anymore: x=().

How can I quickly access the last argument from the last command (!$ in bash)?

If you have compiled rc23 with readline(3), simply type alt+_ (underscore).

Is there an equivalent of "sudo !!" from bash?

Once again, we can use readline(3) to do something similar. Add the following lines to your $home/.inputrc:

$if rc23
  Control-t: "\C-p\C-asudo \C-e"
$endif

After restarting rc23, when you type Ctrl+T, the last command will be brought back prefixed with "sudo".

Why can't I use ~ (tilde) as a shortcut for $HOME anymore?

In rc shells, tilde is the match operator, it does not mean "home". That said, if compiled with readline(3), there is an option to use ~ when autocompleting paths. Add these lines to your $home/.inputrc:

$if rc23
  set expand-tilde on
$endif

Now, when typing a file path starting with a tilde, press Tab and it will be replaced by your $home.

What is the quickest way to check if a variable is defined?

The correct way is:

if (!~ $x ()) ...
  or
if (!~ $#x 0) ...

but since () essentially means nothing, you can omit it:

if (!~ $x) ...

Thus, to set a default value to a variable:

~ $x && x = 'abc'

Is rc23 compatible with Plan 9 rc?

Mostly. The syntax should be fully compatible. We are using the official test.rc from Plan 9 to verify the parsing. Please refer to the Incompatibilities section of the manual page for a list of known differences. I hope to remove a bunch of those in the future.

Did you use AI?

None of the rc23 source code, nor its documentation, has been generated by AI and I intend to keep it that way. AI-generated merge requests will be rejected.

What's your Discord server?

I hope you are joking. We do have an IRC channel: irc.oftc.net/#rc23.