Unix V5, OpenBSD, Plan 9, FreeBSD, and GNU implementations of echo.c
gist.github.com
gist.github.com
$type echo
echo is a shell builtinIt's calling "write", which is a system call. Without the buffer, it would call write once for argv, and once for the newline if nflag is not set. Calling a system call twice would result in twice as many context switches, and thus be very slightly slower.
Speaking of clever tricks, would it be faster or slower to use c99's variable-length arrays instead of malloc?
But a single malloc is nothing compared to the cost of a context switch into kernel mode.
#define BUF_INITIAL 1024
char buf[BUF_INITIAL];
int main(int argc, char** argv)
{
char* p;
...
p = len > BUF_INITIAL ? malloc(len) : buf;
...
write(1, p, len);
...
}I'm also guessing that 4k was chosen because a malloc() of 1 page is faster than a malloc() of >1 page. Of course that's with the assumption that the systems use a 4k page size.
4 KB * (many) could be frightening.
int main (int argc, const char * argv[])
{
for( int i = 1; i < argc; ++i)
{
((char *)argv[i])[-1] = ' ';
}
size_t len = strlen(argv[argc - 1]);
((char *)argv[argc - 1])[len] = '\n';
write(1, argv[1], argv[argc - 1] - argv[1] + len + 1);
return EXIT_SUCCESS;
}"And why bother with stdio (or its Plan 9 equivalent) when it's so easy to do the buffering faster yourself?"
For the same reason you'd use any abstraction: to keep your program simpler. Even if you consider it "so easy" there are plenty of opportunities there for off-by-one errors and buffer overflows that would disappear if you used stdio instead.
My earliest UNIX programming was in the mid-80s on a machine with a (luxurious!) 1.5MB of RAM. I definitely remember avoiding stdio when writing small utilities that I wanted to start up and run fast.
Also remember that back in those days echo was not a shell builtin. (Hell, back then the testing operator '[' wasn't even a builtin. Some UNIXes like OS/X still have a vestigial /bin/[ executable!) Programs like echo that ran from shell scripts constantly had to be coded to start up as fast as possible.
Put echo on a diet, removing unnecessary use of stdio and getopt.
Before...
-r-xr-xr-x 1 root wheel 58636 Oct 28 05:16 /bin/echo
After...
-rwxr-xr-x 1 root wheel 12824 Nov 12 17:39 /usr/obj/usr/src/bin/echo/echo
http://svnweb.freebsd.org/base?view=revision&revision=10...This includes linux, but it's in /usr/bin/[
V6: http://www.bsdlover.cn/study/UnixTree/V6/usr/source/s1/cat.s... V7: http://www.bsdlover.cn/study/UnixTree/V7/usr/src/cmd/cat.c.h... Plan 9: http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cat.c BSD: http://www.koders.com/c/fidF501905968D8BE7BBDD355C3C8DB62804... GNU: http://git.savannah.gnu.org/cgit/coreutils.git/plain/src/cat...
However, unlike the minimal version often seen, GNU Hello processes its argument list to modify its behavior, supports greetings in many languages, and so on. The primary purpose of GNU Hello is to demonstrate how to write other programs that do these things; it serves as a model for GNU coding standards and GNU maintainer practices."
(alluding to a quote from him that I can’t source, “One of my most productive days was throwing away 1000 lines of code.”)
See also, "UNIX Style, or cat -v Considered Harmful" (http://harmful.cat-v.org/cat-v/).
It seems telling that the GNU echo's source is "derived from code echo.c in Bash."
For example, instead of cat -v, you'd have a second utility called, 'nonprint' which would just translate non-printing chars, and you'd call it using
cat file1 file2 | nonprintIf you need more features from a basic utility like echo or cat you should create your own version, maybe with a slightly different name, and leave the original as it is.
Those crazy kids at Berkeley cooked up BSD, which was written to meet their needs and subsequently forked into a few variants. The GNU people made a GNU collection of core utilities that met their particular needs and desires.
The Unix nerds at my University felt as you did, and ran a Unix System 5 variant into the late 90's.
The UNIX style promoted in the "cat -v Considered Harmful" paper may have made sense at one time, but it doesn't make sense anymore. For example:
It seems that UNIX has become the victim of cancerous growth at the hands of organizations such as UCB. 4.2BSD is an order of magnitude larger than Version 5, but, Pike claims, not ten times better.
This logic gives the same consideration to people who are digging in the source code for these utilities as to people who actually use them. When you consider the relative numbers, that's a very elitist attitude (for some value of "elite").
Also consider the explanation given in another comment for why "cat -n" is unnecessary:
If you want to number a file's lines, 'echo ,n | ed file | sed 1d' or 'awk ''{ print NR " " $0 }''' will do just fine.
Munging text like that is a pretty common skill for Unix users, but by no means universal. If the man page for cat is pretty simple and readable, and the feature doesn't bloat the code to the point of causing maintenance problems, and there's somebody who's willing to write the code, then enabling "cat -n" a win for users.
In a real system, even basic utilities rarely looks like a CS101 homework result. This is perfectly fine, especially in this case the amount of feature in gnu echo is perfectly reasonable, and the size of the executable will likely depends on various header than code size when the code is so small anyway.
If you need a basic utility like echo or cat, you should create your own version and don't bother others with it.
13 .text 000028dc 08048b90 08048b90 00000b90 24 CONTENTS, ALLOC, LOAD, READONLY, CODE
/bin/cat on debian sid (x86):
13 .text 0000775c 080491b0 080491b0 000011b0 24 CONTENTS, ALLOC, LOAD, READONLY, CODE
That's 12kb and 32kb, respectively. It may have been a lot back in the day, but it's plenty small enough on today's systems.
All of these tools are intended to be composable, analogous to functions. 'cat -v' is like a function with too many arguments, one that does too much. If you need, for example, to allocate a block of zero'd memory, you don't add new flags to malloc(); you use memset() after allocation, you write a for loop, or you use calloc().
Likewise, the basic tools available on Unix can and should be thought of as functions, which take some number of arguments, and the implicit argument of an input channel. They produce as their output an integer as a return value and two output channels, stdout and stderr. Making a function that does too much (and this is as subjective for functions as it is for command-line tools) is known to be bad practice, but for the shell, it is often misunderstood. To misunderstand this is to misunderstand the core principals of the Unix environment.
It has nothing to do with the typical non-programmer user. On, say, Linux or OSX, the user doesn't write functions or talk to the shell very often. They click buttons in a GUI that doesn't in a meaningful sense offer composable programs, and it's an inefficient but simple way to interact with the machine, a way that matches their habits and understanding. cat, echo, sed, and awk aren't for these users; they're for programmers, and the typical user does not know or care whether cat can show non-printing characters, but as a programmer, I certainly care about a clean design for my environment.
The "-n" special case opened the floodgates for many more options. And what if I actually wanted to print "-n"? There's no way to do it.
Good point.I first tried "echo \-n", and "echo -- -n". No luck. I get the correct visual effect with "echo - ^Hn" (^H generated with ^V, backspace), but the embedded backspace is still actually part of the output :P "echo - ^Hn | col" strips it. Seems like quite an oversight, really, and is prime example of how bugs sneak into code via features.
edit: ilikejam solved it w/o leaning on other tool. As he said "not pretty", but simpler than what I have: http://news.ycombinator.com/item?id=2781034
% env POSIXLY_CORRECT=1 echo -n
-n
Also note that if your shell is Bash, then your echo is a built-in: % type echo
echo is a shell builtin
That's why 'env' is necessary. kamloops$ uname -s
NetBSD
kamloops$ echo $0
sh
kamloops$ env POSIXLY_CORRECT=1 echo -n
kamloops$dave@cronus $ ./echo -n "-n
dave@cronus > "
-n
dave@cronus $
Not pretty, though.
bash-3.2$ echo -n -n foo
foobash-3.2$
Like always with echo -n, no matter what you try, it's not portable.[dave@mini ~]$ echo -n "-n foo
> "
-n foo
[dave@mini ~]$
Easy!
For similar reasons,
echo "" -n
does not work, etc." ' works fine with the bash built-in. It echo's "-n".
If a system has printf, and I think all recent Unix-like systems do, then it works at least 98% the same.
http://www.opensource.apple.com/source/shell_cmds/shell_cmds...
It's close to the FreeBSD implementation.
-r recursive (i.e., it should always be recursive mode if a command operates on files, and should always exist if it makes sense for that command)
-v verbose
-s sort
-i ignore case
-q quiet (suppress output)
If there were, say, 20 well-chosen standard flags (and they were enforced) it could have given the UNIX tools another level of nice regularity.
So rather than this:
void test()
{
int *x = malloc(1000);
int *y = malloc(1000);
for(int i = 0; i < 999; i++) {
if(badThing) {
free(x);
free(y);
return;
}
doStuff()
if(otherBadThing) {
free(x);
free(y);
return;
}
}
free(x);
free(y);
return;
}
You'd have: void test()
{
int *x = malloc(1000);
int *y = malloc(1000);
for(int i = 0; i < 999; i++) {
if(badThing)
goto ret;
doStuff()
if(otherBadThing)
goto ret;
}
ret:
free(x);
free(y);
return;
} void do_stuff(char *name) {
char *my_name = strdup(name);
if (condition_signalling_no_work()) goto done;
if (do_something_with(my_name)) goto done;
...
done:
free(my_name);
return;
}
If it helps think of it as a finally block. The function does some things and no matter what it has to run that block at the end.The way it is used in that code is pretty horrible, IMO, and splitting it into functions and using "return" in place of goto would have been far better.
[1] http://git.savannah.gnu.org/cgit/bash.git/plain/builtins/ech... http://git.savannah.gnu.org/cgit/bash.git/plain/support/rech... http://git.savannah.gnu.org/cgit/bash.git/plain/support/zech...
> while still doing nothing new
Really? So the GNU implementation is an exact match for the functionality of the SysV implementation? If so, I have a bridge I'd like to sell you...