Secho was inspired by a post to the Plan 9 mailing list, where, after a round of bashing on the Unix implementation of echo, the discussion went to bashing a hypothetical FSF-style implementation of echo and eventually to:


echo: echo arguments


echo [-1abCDEeilmNnOqrtuVvwXx] [-B base] [-c cmd] [-d char] [-f file] [-L len] [-o file] [-S voice] [-s char] [args…]


echo outputs its arguments. It takes the following switches:

One argument per line.
Output in ASCII. The default.
-B base
Output in given base, 2..32. Unless -u also given, base > 10 shows lowercase.
Output in binary.
Don’t echo anything, just print the number of fields.
-c cmd
Run cmd on each argument, replacing $? with the argument itself.
Output in decimal.
-d char
Field delimiter. Default is end of argument.
Print to standard error instead of to standard output.
Allow escape sequences
-f file
Read from file, then from command line (if any).
Read arguments from standard input.
-L len
Line width set to len. Default is to ignore line lengths.
Turn uppercase to lowercase.
Multi-column output.
One field per line, numbering each field.
Suppress newline.
Output in octal.
-o file
Write to file instead of standard output.
“Quiet mode:” redirect output to /dev/null if not to a file.
Print every string that matches each regular expression. Regular expressions cannot contain + or * modifiers.
-S voice
Send to speaker, having the given voice say it. If voice is a null string, use the default voice.
-s char
Separate fields with char, default space.
Separate fields with tabs.
Convert lowercase to uppercase. With -B, output in uppercase letters for base > 10.
Strip non-printing characters.
Make non-printing characters visible.
If -L is given, word wrap instead of character wrap. Otherwise, ignored.
Output in uppercase hexadecimal.
Output in lowercase hexadecimal.

Test for everyone: write this echo in as little code as possible. C or rc is permitted. The rules:

  • for C: either Standard C (no other libraries) or only libc (no other Plan 9 libraries)
  • for rc: only use programs in the core Plan 9 distribution - no programs that I have to get myself
  • match the behavior EXACTLY as above
  • shortest code and fastest run time wins

Winner gets something cool.

Oh ho,” said I, “A challenge!

Source Code

How silly of me; I go to all this trouble to put long options in and I forgot to add --version or --copyright.
It just isn’t the full modern Unix experience unless the program accepts long options.

Time to add a new feature!

The -R option prints out the ascii codes for each character, but to improve readability it does it in roman numerals.

Oh, look, the code wasn’t “portable” to FreeBSD 4.10. Tch, tch, I’ll have to fix that. And while I’m at it, let me fix the feature that stopped -r from working when you specified non-ascii output (I was running the re on the formatted buffer, which may be useful if you’ve memorized ascii->base13 conversions, but is not so useful if you’re a mortal.)
Oh, what the hell, let’s make this code “portable” and add a few more features:
  1. Implement -L & -w.
  2. Use a STRING(regex) to hold regular expressions, so I can have a potentially unlimited number of them.
  3. Have Cprintf() write directly into the destination Cstring instead of using malloced memory and a copy.
  4. Use xmalloc() and xrealloc() so that failed memory allocations will properly error out.
  5. Use to make this code “portable”.
  6. use a built-in basename() if a reentrant basename can’t be found.
  7. Add a copyright notice.
  8. And some comments.

Three hours of coding resulted in v0.01, which implements most of the flags in the challenge. V0.01 doesn’t implement -L, -w, -m, and -S, plus it doesn’t implement -r exactly as specified (I use Henry Spencer’s regex for the regular expressions, and it implements *, so I’d need to hack it to not support that feature and it’s not that important to be feature-complete.)

To make up for losing these features, I’ve added -0 (when taking input from a file or stdin, fields are separated by nulls), -9 (Plan 9 compatability – if no arguments are given, don’t print the trailing newline.), and -? (spit out a usage message.)


Like every other piece of code I’m writing these days, I’m keeping track of code bloat. You can too!