~sircmpwn/ctools

echo: Improve POSIX compliance v4 PROPOSED

Haelwenn (lanodan) Monnier
Haelwenn (lanodan) Monnier: 3
 echo: Improve POSIX compliance
 echo: Improve POSIX compliance
 echo: Improve POSIX compliance

 9 files changed, 144 insertions(+), 318 deletions(-)
This fails all of the tests for me. Otherwise it looks good.
NACK, we deliberately nerfed the echo implementation to get people to
stop using it.
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/ctools/patches/9662/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH v4] echo: Improve POSIX compliance Export this patch

Haelwenn (lanodan) Monnier
* `--` is to be treated as a string so getopt() can’t be used
* -n is implementation-defined, treating it as a string as there is no options
* Remove XSI support, simplifying the whole thing
* Buffer arguments to write(2) then only once
---
 doc/echo.1.scd |  37 +++---------------
 src/echo.c     | 100 ++++++++++++-------------------------------------
 test/echo      |  28 ++++++++++----
 3 files changed, 49 insertions(+), 116 deletions(-)

diff --git a/doc/echo.1.scd b/doc/echo.1.scd
index 0ddc7e6..261eae2 100644
--- a/doc/echo.1.scd
+++ b/doc/echo.1.scd
@@ -6,7 +6,7 @@ echo - writes arguments to the standard output

# SYNOPSIS

*echo* [-n] [_string_...]
*echo* [_string_...]

# DESCRIPTION

@@ -17,41 +17,14 @@ is still printed.

# OPTIONS

*-n*
	Supress the output of a final newline after the last argument
ctools echo does not support any options. Every argument, including --
and -n, are treated as a string operand and printed verbatim. This is
compatible with POSIX echo as specified *without* the XSI option.

# OPERANDS

	_string_
		string to be written to standard output. If -n is used as argument 
		the following escape characters will be recognized:

		\\a
			<alert>
		
		\\b
			<backspace>
		
		\\c
			Suppress the final <newline> and all characters following
		
		\\f
			<form-feed>

		\\n
			<newline>

		\\r
			<carrieage-return>
		
		\\t
			<tab>
		
		\\v 
			<vertical-tab>
		
		\\\\
			<backslash>
		string to be written to standard output.

# DISCLAIMER

diff --git a/src/echo.c b/src/echo.c
index fcd33df..44556a5 100644
--- a/src/echo.c
+++ b/src/echo.c
@@ -1,87 +1,35 @@
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>

static void
usage(void)
{
	fprintf(stderr, "usage: echo [-n] [string...]");
}

int
main(int argc, char *argv[])
{
	int nflag = 0;
	char opt;

	while ((opt = getopt(argc, argv, "n")) != -1) {
		switch (opt) {
		case 'n':
			nflag = 1;
			break;
		default:
			usage();
			return 1;
		}
	}
// Dance required for at least GNU libc
#ifndef ARG_MAX
#	ifdef _POSIX_ARG_MAX
#		define ARG_MAX _POSIX_ARG_MAX
#	else
#		define ARG_MAX 4096
#	endif
#endif

	for (int i = optind; i < argc; ++i) {
		char *arg = NULL;
		for (arg = argv[i]; *arg; arg++) {
			if (arg[0] == '\\' && nflag) {
				switch (arg[1]) {
				case 'a':
					putchar('\a');
					arg++;
					break;
				case 'b':
					putchar('\b');
					arg++;
					break;
				case 'c':
					putchar('\n');
					arg++;
					return 0;
				case 'f':
					putchar('\f');
					arg++;
					break;
				case 'n':
					putchar('\n');
					arg++;
					break;
				case 'r':
					putchar('\r');
					arg++;
					break;
				case 't':
					putchar('\t');
					arg++;
					break;
				case 'v':
					putchar('\v');
					arg++;
					break;
				case '\\':
					putchar('\\');
					arg++;
					break;
				default:
					putchar(arg[0]);
					break;
				}
			} else {
				putchar(arg[0]);
			}
		}
		if (i != argc - 1) {
			putchar(' ');
		}
	}
	// Use ARG_MAX assuming it counts one character between each argument
	char args[ARG_MAX];
	size_t freesize = ARG_MAX;

	strncat(args, argv[1], freesize);
	freesize -= strlen(argv[1]);

	if (!nflag) {
		putchar('\n');
	for (int i = 2; i < argc && freesize > 1; i++) {
		strncat(args, " ", freesize);
		freesize--;
		strncat(args, argv[i], freesize);
		freesize -= strlen(argv[i]);
	}

	printf("%s\n", args);

	return 0;
}
diff --git a/test/echo b/test/echo
index 3004665..47460c6 100644
--- a/test/echo
+++ b/test/echo
@@ -12,18 +12,30 @@ should_handle_two_string() (
	[ "$ct" = "this is a test string this second string" ]
)

should_handle_tab_char() (
	ct="$(../build/echo -n "\tthis is a test string")"
	[ "$ct" = '	this is a test string' ]
should_not_handle_tab_char() (
	ct="$(../build/echo "\tthis is a test string")"
	[ "$ct" = '\tthis is a test string' ]
)

should_handle_c_escape_char() (
	ct="$(../build/echo -n "this is a \ctest string")"
	[ "$ct" = "this is a " ]
should_not_handle_c_escape_char() (
	ct="$(../build/echo "this is a \ctest string")"
	[ "$ct" = "this is a \ctest string" ]
)

should_not_handle_double_dash() {
	ct="$(./echo -- "this is a test string")"
	[ "$ct" = "-- this is a test string" ]
}

should_not_handle_dash_n() {
	ct="$(./echo -n "this is a test string")"
	[ "$ct" = "-n this is a test string" ]
}

runtests \
	should_handle_one_string \
	should_handle_two_string \
	should_handle_tab_char \
	should_handle_c_escape_char
	should_not_handle_tab_char \
	should_not_handle_c_escape_char \
	should_not_handle_double_dash \
	should_not_handle_dash_n
-- 
2.24.1
Ah, this needs to be rebased. It doesn't apply cleanly. Sorry.

[PATCH v5] echo: Improve POSIX compliance Export this patch

Haelwenn (lanodan) Monnier
* `--` is to be treated as a string so getopt() can’t be used
* -n is implementation-defined, treating it as a string as there is no options
* Remove XSI support, simplifying the whole thing
* Buffer arguments to write(2) then only once
---
 doc/echo.1.scd |  37 +++---------------
 src/echo.c     | 100 ++++++++++++-------------------------------------
 test/echo      |  28 ++++++++++----
 3 files changed, 49 insertions(+), 116 deletions(-)

diff --git a/doc/echo.1.scd b/doc/echo.1.scd
index 0ddc7e6..261eae2 100644
--- a/doc/echo.1.scd
+++ b/doc/echo.1.scd
@@ -6,7 +6,7 @@ echo - writes arguments to the standard output

# SYNOPSIS

*echo* [-n] [_string_...]
*echo* [_string_...]

# DESCRIPTION

@@ -17,41 +17,14 @@ is still printed.

# OPTIONS

*-n*
	Supress the output of a final newline after the last argument
ctools echo does not support any options. Every argument, including --
and -n, are treated as a string operand and printed verbatim. This is
compatible with POSIX echo as specified *without* the XSI option.

# OPERANDS

	_string_
		string to be written to standard output. If -n is used as argument 
		the following escape characters will be recognized:

		\\a
			<alert>
		
		\\b
			<backspace>
		
		\\c
			Suppress the final <newline> and all characters following
		
		\\f
			<form-feed>

		\\n
			<newline>

		\\r
			<carrieage-return>
		
		\\t
			<tab>
		
		\\v 
			<vertical-tab>
		
		\\\\
			<backslash>
		string to be written to standard output.

# DISCLAIMER

diff --git a/src/echo.c b/src/echo.c
index fcd33df..44556a5 100644
--- a/src/echo.c
+++ b/src/echo.c
@@ -1,87 +1,35 @@
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>

static void
usage(void)
{
	fprintf(stderr, "usage: echo [-n] [string...]");
}

int
main(int argc, char *argv[])
{
	int nflag = 0;
	char opt;

	while ((opt = getopt(argc, argv, "n")) != -1) {
		switch (opt) {
		case 'n':
			nflag = 1;
			break;
		default:
			usage();
			return 1;
		}
	}
// Dance required for at least GNU libc
#ifndef ARG_MAX
#	ifdef _POSIX_ARG_MAX
#		define ARG_MAX _POSIX_ARG_MAX
#	else
#		define ARG_MAX 4096
#	endif
#endif

	for (int i = optind; i < argc; ++i) {
		char *arg = NULL;
		for (arg = argv[i]; *arg; arg++) {
			if (arg[0] == '\\' && nflag) {
				switch (arg[1]) {
				case 'a':
					putchar('\a');
					arg++;
					break;
				case 'b':
					putchar('\b');
					arg++;
					break;
				case 'c':
					putchar('\n');
					arg++;
					return 0;
				case 'f':
					putchar('\f');
					arg++;
					break;
				case 'n':
					putchar('\n');
					arg++;
					break;
				case 'r':
					putchar('\r');
					arg++;
					break;
				case 't':
					putchar('\t');
					arg++;
					break;
				case 'v':
					putchar('\v');
					arg++;
					break;
				case '\\':
					putchar('\\');
					arg++;
					break;
				default:
					putchar(arg[0]);
					break;
				}
			} else {
				putchar(arg[0]);
			}
		}
		if (i != argc - 1) {
			putchar(' ');
		}
	}
	// Use ARG_MAX assuming it counts one character between each argument
	char args[ARG_MAX];
	size_t freesize = ARG_MAX;

	strncat(args, argv[1], freesize);
	freesize -= strlen(argv[1]);

	if (!nflag) {
		putchar('\n');
	for (int i = 2; i < argc && freesize > 1; i++) {
		strncat(args, " ", freesize);
		freesize--;
		strncat(args, argv[i], freesize);
		freesize -= strlen(argv[i]);
	}

	printf("%s\n", args);

	return 0;
}
diff --git a/test/echo b/test/echo
index 61f5fbf..fa5061f 100644
--- a/test/echo
+++ b/test/echo
@@ -12,18 +12,30 @@ should_handle_two_string() (
	[ "$ct" = "this is a test string this second string" ]
)

should_handle_tab_char() (
	ct="$(./echo -n "\tthis is a test string")"
	[ "$ct" = '	this is a test string' ]
should_not_handle_tab_char() (
	ct="$(../build/echo "\tthis is a test string")"
	[ "$ct" = '\tthis is a test string' ]
)

should_handle_c_escape_char() (
	ct="$(./echo -n "this is a \ctest string")"
	[ "$ct" = "this is a " ]
should_not_handle_c_escape_char() (
	ct="$(../build/echo "this is a \ctest string")"
	[ "$ct" = "this is a \ctest string" ]
)

should_not_handle_double_dash() {
	ct="$(./echo -- "this is a test string")"
	[ "$ct" = "-- this is a test string" ]
}

should_not_handle_dash_n() {
	ct="$(./echo -n "this is a test string")"
	[ "$ct" = "-n this is a test string" ]
}

runtests \
	should_handle_one_string \
	should_handle_two_string \
	should_handle_tab_char \
	should_handle_c_escape_char
	should_not_handle_tab_char \
	should_not_handle_c_escape_char \
	should_not_handle_double_dash \
	should_not_handle_dash_n
-- 
2.24.1
This fails all of the tests for me. Otherwise it looks good.

[PATCH v6] echo: Improve POSIX compliance Export this patch

Haelwenn (lanodan) Monnier
* `--` is to be treated as a string so getopt() can’t be used
* -n is implementation-defined, treating it as a string as there is no options
* Remove XSI support, simplifying the whole thing
* Buffer arguments to call write(2) only once
---
 doc/echo.1.scd |  5 ++-
 src/echo.c     | 99 ++++++++++++--------------------------------------
 test/echo      | 28 ++++++++++----
 3 files changed, 46 insertions(+), 86 deletions(-)

diff --git a/doc/echo.1.scd b/doc/echo.1.scd
index 0ddc7e6..e450ba3 100644
--- a/doc/echo.1.scd
+++ b/doc/echo.1.scd
@@ -17,8 +17,9 @@ is still printed.

# OPTIONS

*-n*
	Supress the output of a final newline after the last argument
ctools echo does not support any options. Every argument, including --
and -n, are treated as a string operand and printed verbatim. This is
compatible with POSIX echo as specified *without* the XSI option.

# OPERANDS

diff --git a/src/echo.c b/src/echo.c
index fcd33df..2757494 100644
--- a/src/echo.c
+++ b/src/echo.c
@@ -1,87 +1,34 @@
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <unistd.h>

static void
usage(void)
{
	fprintf(stderr, "usage: echo [-n] [string...]");
}

int
main(int argc, char *argv[])
{
	int nflag = 0;
	char opt;

	while ((opt = getopt(argc, argv, "n")) != -1) {
		switch (opt) {
		case 'n':
			nflag = 1;
			break;
		default:
			usage();
			return 1;
		}
	}
#ifndef ARG_MAX
#	ifdef _POSIX_ARG_MAX
#		define ARG_MAX _POSIX_ARG_MAX
#	else
#		define ARG_MAX 4096
#	endif
#endif

	for (int i = optind; i < argc; ++i) {
		char *arg = NULL;
		for (arg = argv[i]; *arg; arg++) {
			if (arg[0] == '\\' && nflag) {
				switch (arg[1]) {
				case 'a':
					putchar('\a');
					arg++;
					break;
				case 'b':
					putchar('\b');
					arg++;
					break;
				case 'c':
					putchar('\n');
					arg++;
					return 0;
				case 'f':
					putchar('\f');
					arg++;
					break;
				case 'n':
					putchar('\n');
					arg++;
					break;
				case 'r':
					putchar('\r');
					arg++;
					break;
				case 't':
					putchar('\t');
					arg++;
					break;
				case 'v':
					putchar('\v');
					arg++;
					break;
				case '\\':
					putchar('\\');
					arg++;
					break;
				default:
					putchar(arg[0]);
					break;
				}
			} else {
				putchar(arg[0]);
			}
		}
		if (i != argc - 1) {
			putchar(' ');
		}
	}
	// Use ARG_MAX assuming it counts one character between each argument
	char arguments[ARG_MAX];
	size_t freesize = ARG_MAX;

	strncat(arguments, argv[1], freesize);
	freesize -= strlen(argv[1]);

	if (!nflag) {
		putchar('\n');
	for (int i = 2; i < argc && freesize > 1; i++) {
		strcat(arguments, " ");
		freesize--;
		strncat(arguments, argv[i], freesize - 1);
		freesize -= strlen(argv[i]);
	}

	printf("%s\n", arguments);

	return 0;
}
diff --git a/test/echo b/test/echo
index 61f5fbf..e002080 100644
--- a/test/echo
+++ b/test/echo
@@ -12,18 +12,30 @@ should_handle_two_string() (
	[ "$ct" = "this is a test string this second string" ]
)

should_handle_tab_char() (
	ct="$(./echo -n "\tthis is a test string")"
	[ "$ct" = '	this is a test string' ]
should_not_handle_tab_char() (
	ct="$(./echo "\tthis is a test string")"
	[ "$ct" = '\tthis is a test string' ]
)

should_handle_c_escape_char() (
	ct="$(./echo -n "this is a \ctest string")"
	[ "$ct" = "this is a " ]
should_not_handle_c_escape_char() (
	ct="$(./echo "this is a \ctest string")"
	[ "$ct" = "this is a \ctest string" ]
)

should_not_handle_double_dash() {
	ct="$(./echo -- "this is a test string")"
	[ "$ct" = "-- this is a test string" ]
}

should_not_handle_dash_n() {
	ct="$(./echo -n "this is a test string")"
	[ "$ct" = "-n this is a test string" ]
}

runtests \
	should_handle_one_string \
	should_handle_two_string \
	should_handle_tab_char \
	should_handle_c_escape_char
	should_not_handle_tab_char \
	should_not_handle_c_escape_char \
	should_not_handle_double_dash \
	should_not_handle_dash_n
-- 
2.26.2
NACK, we deliberately nerfed the echo implementation to get people to
stop using it.