commit: d63f75d6def87d5b2d5a602bf04759eb2168b1cd
parent d918b06f27b8a2dff1ea3cc292b00a1d533a358a
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Fri, 3 Dec 2021 23:57:39 +0100
bin/echo: Make it posix-compliant and plan9-style
Diffstat:
5 files changed, 103 insertions(+), 12 deletions(-)
diff --git a/bin/Makefile.config b/bin/Makefile.config
@@ -1,2 +1,2 @@
EXE = args basename cat date del dirname echo false humanize lolcat mdate pwd range sizeof sname strings tee true tty xcd
-MAN1 = basename.1 date.1 del.1 dirname.1 humanize.1 lolcat.1 sname.1
+MAN1 = basename.1 date.1 del.1 dirname.1 echo.1 humanize.1 lolcat.1 sname.1
diff --git a/bin/echo.1 b/bin/echo.1
@@ -0,0 +1,32 @@
+.\" Collection of Unix tools, comparable to coreutils
+.\" Copyright 2017-2021 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
+.\" SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
+.Dd 2021-04-05
+.Dt ECHO 1
+.Os
+.Sh NAME
+.Nm echo
+.Nd write arguments to standard output
+.Sh SYNOPSIS
+.Nm
+.Op Ar string ...
+.Sh DESCRIPTION
+.Nm
+buffers all of it's arguments and writes them all at once to standard output, followed by a newline.
+If there is no arguments, only the newline is written.
+.Pp
+Should also be noted that this version of
+.Nm
+isn't XSI-compliant as arguments like
+.Fl Ar n
+are parsed as strings and backslash operators aren't supported.
+See
+.Xr printf 1
+for such an utility.
+.Sh EXIT STATUS
+.Ex -std
+.Sh STANDARDS
+Not XSI-compliant but should be compliant to
+.St -p1003.1-2008
+.Sh AUTHORS
+.An Haelwenn (lanodan) Monnier Aq Mt contact@hacktivis.me
diff --git a/bin/echo.c b/bin/echo.c
@@ -3,19 +3,55 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
#define _POSIX_C_SOURCE 200809L
-#include <stdio.h> /* printf(), putchar() */
+#include <stdio.h> /* perror */
+#include <stdlib.h> /* malloc */
+#include <string.h> /* strlen */
+#include <unistd.h> /* write */
-// Note: maybe consider buffering to write(2) only once like in Plan9
-// https://web.archive.org/web/20150519100236/https://gist.github.com/dchest/1091803#comment-1398629
int
main(int argc, char *argv[])
{
- int i;
+ size_t arg_len = 0;
+ char *buffer, *buffer_p;
+ int err = 0;
- for(i = 1; i < argc; i++)
- printf("%s ", argv[i]);
+ if(argc < 2)
+ {
+ arg_len++;
+ }
- putchar('\n');
+ for(int i = 1; i < argc; i++)
+ {
+ arg_len += strlen(argv[i]) + 1; // str + space|newline
+ }
- return 0;
+ buffer = malloc(arg_len);
+ if(buffer == NULL)
+ {
+ perror("malloc(arg_len)");
+ return 1;
+ }
+
+ buffer_p = buffer;
+ for(int i = 1; i < argc; i++)
+ {
+ buffer_p = strcpy(buffer_p, argv[i]);
+ buffer_p += strlen(buffer_p);
+
+ if(i < argc - 1)
+ {
+ *buffer_p++ = ' ';
+ }
+ }
+ *buffer_p++ = '\n';
+
+ if(write(1, buffer, arg_len) < (ssize_t)arg_len)
+ {
+ perror("write(1, buffer, arg_len)");
+ err++;
+ }
+
+ free(buffer);
+
+ return err;
}
diff --git a/test-bin/Kyuafile b/test-bin/Kyuafile
@@ -4,12 +4,13 @@ test_suite("utils")
-- /BEGIN/,$ | LC_ALL=C.UTF-8 sort
atf_test_program{name="args"}
-atf_test_program{name="cat"}
-atf_test_program{name="tee"}
atf_test_program{name="basename"}
+atf_test_program{name="cat"}
atf_test_program{name="dirname"}
+atf_test_program{name="echo"}
+atf_test_program{name="false"}
atf_test_program{name="humanize"}
atf_test_program{name="sname"}
+atf_test_program{name="tee"}
atf_test_program{name="true"}
-atf_test_program{name="false"}
atf_test_program{name="xcd"}
diff --git a/test-bin/echo b/test-bin/echo
@@ -0,0 +1,22 @@
+#!/usr/bin/env atf-sh
+atf_test_case empty
+empty_body() {
+ atf_check -o "inline:\n" ../bin/echo
+}
+
+atf_test_case hello
+hello_body() {
+ atf_check -o "inline:hello world\n" ../bin/echo hello world
+}
+
+atf_test_case doubledash
+doubledash_body() {
+ atf_check -o "inline:-- hello\n" ../bin/echo -- hello
+}
+
+atf_init_test_cases() {
+ cd "$(atf_get_srcdir)"
+ atf_add_test_case empty
+ atf_add_test_case hello
+ atf_add_test_case doubledash
+}