public
Last active

UNIX V5, OpenBSD, Plan 9, FreeBSD, and GNU coreutils implementations of echo.c

  • Download Gist
0 - UNIX Fifth Edition
1 2 3 4 5 6 7 8 9 10
main(argc, argv)
int argc;
char *argv[];
{
int i;
 
argc--;
for(i=1; i<=argc; i++)
printf("%s%c", argv[i], i==argc? '\n': ' ');
}
1 - OpenBSD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
/* $OpenBSD: echo.c,v 1.7 2009/10/27 23:59:21 deraadt Exp $ */
/* $NetBSD: echo.c,v 1.6 1995/03/21 09:04:27 cgd Exp $ */
 
/*
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
/* ARGSUSED */
int
main(int argc, char *argv[])
{
int nflag;
 
/* This utility may NOT do getopt(3) option parsing. */
if (*++argv && !strcmp(*argv, "-n")) {
++argv;
nflag = 1;
}
else
nflag = 0;
 
while (*argv) {
(void)fputs(*argv, stdout);
if (*++argv)
putchar(' ');
}
if (!nflag)
putchar('\n');
 
return 0;
}
2 - Plan 9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
#include <u.h>
#include <libc.h>
 
void
main(int argc, char *argv[])
{
int nflag;
int i, len;
char *buf, *p;
 
nflag = 0;
if(argc > 1 && strcmp(argv[1], "-n") == 0)
nflag = 1;
 
len = 1;
for(i = 1+nflag; i < argc; i++)
len += strlen(argv[i])+1;
 
buf = malloc(len);
if(buf == 0)
exits("no memory");
 
p = buf;
for(i = 1+nflag; i < argc; i++){
strcpy(p, argv[i]);
p += strlen(p);
if(i < argc-1)
*p++ = ' ';
}
if(!nflag)
*p++ = '\n';
 
if(write(1, buf, p-buf) < 0){
fprint(2, "echo: write error: %r\n");
exits("write error");
}
 
exits((char *)0);
}
3 - FreeBSD
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
/*-
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
 
#if 0
#ifndef lint
static char const copyright[] =
"@(#) Copyright (c) 1989, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
 
#ifndef lint
static char sccsid[] = "@(#)echo.c 8.1 (Berkeley) 5/31/93";
#endif /* not lint */
#endif
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
 
#include <sys/types.h>
#include <sys/uio.h>
 
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
/*
* Report an error and exit.
* Use it instead of err(3) to avoid linking-in stdio.
*/
static __dead2 void
errexit(const char *prog, const char *reason)
{
char *errstr = strerror(errno);
write(STDERR_FILENO, prog, strlen(prog));
write(STDERR_FILENO, ": ", 2);
write(STDERR_FILENO, reason, strlen(reason));
write(STDERR_FILENO, ": ", 2);
write(STDERR_FILENO, errstr, strlen(errstr));
write(STDERR_FILENO, "\n", 1);
exit(1);
}
 
int
main(int argc, char *argv[])
{
int nflag; /* if not set, output a trailing newline. */
int veclen; /* number of writev arguments. */
struct iovec *iov, *vp; /* Elements to write, current element. */
char space[] = " ";
char newline[] = "\n";
char *progname = argv[0];
 
/* This utility may NOT do getopt(3) option parsing. */
if (*++argv && !strcmp(*argv, "-n")) {
++argv;
--argc;
nflag = 1;
} else
nflag = 0;
 
veclen = (argc >= 2) ? (argc - 2) * 2 + 1 : 0;
 
if ((vp = iov = malloc((veclen + 1) * sizeof(struct iovec))) == NULL)
errexit(progname, "malloc");
 
while (argv[0] != NULL) {
size_t len;
 
len = strlen(argv[0]);
 
/*
* If the next argument is NULL then this is this
* the last argument, therefore we need to check
* for a trailing \c.
*/
if (argv[1] == NULL) {
/* is there room for a '\c' and is there one? */
if (len >= 2 &&
argv[0][len - 2] == '\\' &&
argv[0][len - 1] == 'c') {
/* chop it and set the no-newline flag. */
len -= 2;
nflag = 1;
}
}
vp->iov_base = *argv;
vp++->iov_len = len;
if (*++argv) {
vp->iov_base = space;
vp++->iov_len = 1;
}
}
if (!nflag) {
veclen++;
vp->iov_base = newline;
vp++->iov_len = 1;
}
/* assert(veclen == (vp - iov)); */
while (veclen) {
int nwrite;
 
nwrite = (veclen > IOV_MAX) ? IOV_MAX : veclen;
if (writev(STDOUT_FILENO, iov, nwrite) == -1)
errexit(progname, "write");
iov += nwrite;
veclen -= nwrite;
}
return 0;
}
4 - GNU coreutils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
/* echo.c, derived from code echo.c in Bash.
Copyright (C) 1987, 1989, 1991-1997, 1999-2005, 2007-2011 Free Software
Foundation, Inc.
 
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
 
#include <config.h>
#include <stdio.h>
#include <sys/types.h>
#include "system.h"
 
/* The official name of this program (e.g., no `g' prefix). */
#define PROGRAM_NAME "echo"
 
#define AUTHORS \
proper_name ("Brian Fox"), \
proper_name ("Chet Ramey")
 
/* If true, interpret backslash escapes by default. */
#ifndef DEFAULT_ECHO_TO_XPG
enum { DEFAULT_ECHO_TO_XPG = false };
#endif
 
void
usage (int status)
{
if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
printf (_("\
Usage: %s [SHORT-OPTION]... [STRING]...\n\
or: %s LONG-OPTION\n\
"), program_name, program_name);
fputs (_("\
Echo the STRING(s) to standard output.\n\
\n\
-n do not output the trailing newline\n\
"), stdout);
fputs (_(DEFAULT_ECHO_TO_XPG
? N_("\
-e enable interpretation of backslash escapes (default)\n\
-E disable interpretation of backslash escapes\n")
: N_("\
-e enable interpretation of backslash escapes\n\
-E disable interpretation of backslash escapes (default)\n")),
stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
\n\
If -e is in effect, the following sequences are recognized:\n\
\n\
"), stdout);
fputs (_("\
\\\\ backslash\n\
\\a alert (BEL)\n\
\\b backspace\n\
\\c produce no further output\n\
\\e escape\n\
\\f form feed\n\
\\n new line\n\
\\r carriage return\n\
\\t horizontal tab\n\
\\v vertical tab\n\
"), stdout);
fputs (_("\
\\0NNN byte with octal value NNN (1 to 3 digits)\n\
\\xHH byte with hexadecimal value HH (1 to 2 digits)\n\
"), stdout);
printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
emit_ancillary_info ();
}
exit (status);
}
 
/* Convert C from hexadecimal character to integer. */
static int
hextobin (unsigned char c)
{
switch (c)
{
default: return c - '0';
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
}
}
 
/* Print the words in LIST to standard output. If the first word is
`-n', then don't print a trailing newline. We also support the
echo syntax from Version 9 unix systems. */
 
int
main (int argc, char **argv)
{
bool display_return = true;
bool allow_options =
(! getenv ("POSIXLY_CORRECT")
|| (! DEFAULT_ECHO_TO_XPG && 1 < argc && STREQ (argv[1], "-n")));
 
/* System V machines already have a /bin/sh with a v9 behavior.
Use the identical behavior for these machines so that the
existing system shell scripts won't barf. */
bool do_v9 = DEFAULT_ECHO_TO_XPG;
 
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
 
atexit (close_stdout);
 
/* We directly parse options, rather than use parse_long_options, in
order to avoid accepting abbreviations. */
if (allow_options && argc == 2)
{
if (STREQ (argv[1], "--help"))
usage (EXIT_SUCCESS);
 
if (STREQ (argv[1], "--version"))
{
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
(char *) NULL);
exit (EXIT_SUCCESS);
}
}
 
--argc;
++argv;
 
if (allow_options)
while (argc > 0 && *argv[0] == '-')
{
char const *temp = argv[0] + 1;
size_t i;
 
/* If it appears that we are handling options, then make sure that
all of the options specified are actually valid. Otherwise, the
string should just be echoed. */
 
for (i = 0; temp[i]; i++)
switch (temp[i])
{
case 'e': case 'E': case 'n':
break;
default:
goto just_echo;
}
 
if (i == 0)
goto just_echo;
 
/* All of the options in TEMP are valid options to ECHO.
Handle them. */
while (*temp)
switch (*temp++)
{
case 'e':
do_v9 = true;
break;
 
case 'E':
do_v9 = false;
break;
 
case 'n':
display_return = false;
break;
}
 
argc--;
argv++;
}
 
just_echo:
 
if (do_v9)
{
while (argc > 0)
{
char const *s = argv[0];
unsigned char c;
 
while ((c = *s++))
{
if (c == '\\' && *s)
{
switch (c = *s++)
{
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'c': exit (EXIT_SUCCESS);
case 'e': c = '\x1B'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
case 'x':
{
unsigned char ch = *s;
if (! isxdigit (ch))
goto not_an_escape;
s++;
c = hextobin (ch);
ch = *s;
if (isxdigit (ch))
{
s++;
c = c * 16 + hextobin (ch);
}
}
break;
case '0':
c = 0;
if (! ('0' <= *s && *s <= '7'))
break;
c = *s++;
/* Fall through. */
case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c -= '0';
if ('0' <= *s && *s <= '7')
c = c * 8 + (*s++ - '0');
if ('0' <= *s && *s <= '7')
c = c * 8 + (*s++ - '0');
break;
case '\\': break;
 
not_an_escape:
default: putchar ('\\'); break;
}
}
putchar (c);
}
argc--;
argv++;
if (argc > 0)
putchar (' ');
}
}
else
{
while (argc > 0)
{
fputs (argv[0], stdout);
argc--;
argv++;
if (argc > 0)
putchar (' ');
}
}
 
if (display_return)
putchar ('\n');
exit (EXIT_SUCCESS);
}

the original UNIX one was best. The FreeBSD and GNU ones are disgusting.

agree with kylebgorman

@kylebgorman slightly off -- Plan 9 bests UNIX.

this gist fucking nails it.

@rch Plan 9 one is good too.

On Tue, Jul 19, 2011 at 12:38 PM, rch
reply@reply.github.com
wrote:

@kylebgorman slightly off -- Plan 9 bests UNIX.

Reply to this email directly or view it on GitHub:
https://gist.github.com/1091803

Kyle Gorman ~ kgorman@ling.upenn.edu ~ 513 405 2543

gnu echo is pretty bad. Is that a goto? I can't believe that code is running on my machine.

@ericnormand: LOL yes!

@ericnormand goto is recommended by some programming gurus to avoid repetitive error-solving code. @Everybody_Else the programs are not the same... some of them have arguments, other don't... also the complexity of the OS can influence the complexity of the code. Shorter sometimes is not better...

The GNU version is best. none of the other's implement --help to print the program's usage, including specifics on the (also not implemented) -E and -e options, which are useful depending on how your script uses echo. Also, vs the SYS V and other older versions, this version of echo doesn't require a shell to properly handle escape sequences or quotes. It also properly detects and uses locales and characters sets. The FreeBSD version does almost none of these things. Also, given the source code above, it would be a simple matter to reimplement echo more simply. Be careful. Some of that extra functionality may be neccesary for your system to function.

@pyDav the use of goto in the GNU version could be a function call instead.
@mrgernixus --help is trivial and could be grafted onto the Sys V or (Net|Open)BSD one. While -e triggers useful behaviors, I think this kind of behavior that misses the point of echo and should be done in a shell script if you need it for some reason (also having a flag to turn off non-default behavior is stupid, full stop).

I had done something similar for cat.c: https://gist.github.com/665971

@mrgenixus The man pages implement the same funcionality as '--help' nicely.

@pete But they can't tell you what your error is in your particular call. Like "argument should be numeric" or "missing argument" or whatnot.

How often does one even use echo.c, anyway?

scott@optimusprime:~> which echo
echo: shell built-in command

@Leonidas-from-XIV There's no way for the user to screw up in the Plan 9 version, so no need to validate arguments. Only malloc() and write() can fail.

Incidentally, as Plan 9 is always UTF-8, there's no need to handle odd locale settings, either. No reason for -e handling, as escapes can be done in the shell, and thus no need to handle errors on -e. The whole approach is nice, clean, DRY: don't handle problems if you can make them impossible, and don't handle use cases that are handled elsewhere.

@pete So some programs should have manpages, others should react on wrong input when neccessary? Why not have both input validation and documentation like... GNU coreutils?

@raggi: strlen.c thing is kind of misleading: those versions are not really used on most architectures. You'll find different (mostly asm) implementations in respective /arch/ directories. Here's an example for i386:

@ericnormand Yay for cargo-cult fear of goto.

Also, this is not running on your machine. The /bin/echo command is practically never used, since all shells have an echo builtin.

There's a lesson here, but it's not what some people seem to think. We all know that the more time we spend on a project, the more annoyed we get with the code. Sooner or later it always ends up feeling bloated and badly designed. I think there's two reasons:

  • The number of use cases increases. The GNU version undeniably does a lot more than the other versions.
  • The principle of least astonishment. Users don't want programs to be consistent, they want them to do what they want them to do. That requires a lot of extra coding handling all the edge cases.

I think that what we should take away from this is that in many cases complexity in inevitable, and that we should learn to plan for it and handle it. Simplicity isn't always an option.

@Leonidas-from-XIV There's no "wrong" input to the Plan 9 version of echo, so there's no need to handle errors related to bad input. What input validation could the Plan 9 echo do? And everything ought to have documentation, which on Unixes takes the form of man pages, so yes.

Wait, echo -e is specific to GNU?

@ssmccoy
@mernen

$ lsb_release -d
Description: Ubuntu 11.04

$ which echo
/bin/echo

$ echo $BASH_VERSION
4.2.8(1)-release

@brettalton I know the binary is there, I said it's not used. Compare echo --help and /bin/echo --help and you'll see.

I don't know what shell @ssmccoy is using (his which is clearly a builtin), but you can get a similar answer with type echo.

Two more reasons to prefer the GNU version:
Unlike the first two, with GNU echo, "env echo .... > /dev/full" detects and reports the write error.
Unlike the Plan9 and FreeBSD versions, GNU echo does not use malloc, so cannot fail with ENOMEM.

@brettalton @mernen I'm using zsh.

WIndows is missing :(

@shurane I believe echo in Windows is only a shell builtin.

@meyering GNU echo uses putchar(2) which is likely backed by fwrite(2) and thus will malloc a back buffer. Looping over argv and passing the strings to write(2) would be the only way to not malloc and the previous implementers believed the extra syscalls hurt the performance of echo.

@Leonidas-from-XIV I believe Windows is closed-source, anyways.

Somebody finally made it obvious why was GNU/Linux a terrible idea :)

Great Gists!, I've never stopped before to read the implementation of echo in GNU. I really liked the NetBSD, and the classic old Unix implementation, both for their simplicity. FreeBSD was playing with too many intermediate variables. And GNU, losts its way trying to bring some backward compatibility, it actually should have focus just in the option parsing and then executing:

  while (argc > 0)
    {
      fputs (argv[0], stdout);
      argc--;
      argv++;
      if (argc > 0)
        putchar (' ');
    }
    if (display_return)
      putchar ('\n');

Just as a reflection, have you checked the differences between gist-1 and the following provided in the previous comments: http://cvsweb.netbsd.org/bsdweb.cgi/src/bin/echo/echo.c?rev=HEAD
The second one, has some additions, probably added to give support to something new or due to some bug fixes (just guessing here). It has been some kind of evolution between these two versions of the same implementation, the same type of evolution that GNU echo may have followed (I may be wrong here ... but I don't think gist-4 was ugly from the very beginning)... Perhaps it is time for a cleanup...

Thanks for collecting this code here :-)

The System V version is shit for using printf for such a simple program. The OpenBSD version is using strcmp which is notorious for being insecure. All of these implementations could have been done better, but that's why echo is a shell built in function.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.