M STATUS => STATUS +1 -1
@@ 41,7 41,7 @@ T df
T diff
D dirname
T du
-T echo
+ D echo
T ed*
D env
W ex
A doc/echo.1.scd => doc/echo.1.scd +61 -0
@@ 0,0 1,61 @@
+echo(1) "ctools"
+
+# NAME
+
+echo - writes arguments to the standard output
+
+# SYNOPSIS
+
+*echo* [-n] [_string_...]
+
+# DESCRIPTION
+
+*echo* writes each of its arguments to standard out, joined with a
+space, then writes a newline. If no arguments are given, a newline
+is still printed.
+
+
+# OPTIONS
+
+*-n*
+ Supress the output of a final newline after the last argument
+
+# 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>
+
+# DISCLAIMER
+
+This command is part of ctools and is compatible with POSIX-1.2017, and may
+optionally support XSI extensions. This man page is not intended to be a
+complete reference, and where it disagrees with the specification, the
+specification takes precedence.
M doc/meson.build => doc/meson.build +1 -0
@@ 13,6 13,7 @@ man_files = [
'cmp.1',
'comm.1',
'dirname.1',
+ 'echo.1',
'env.1',
'false.1',
'head.1',
M meson.build => meson.build +1 -0
@@ 20,6 20,7 @@ oneshots = [
'cmp',
'comm',
'dirname',
+ 'echo',
'env',
'false',
'head',
A src/echo.c => src/echo.c +87 -0
@@ 0,0 1,87 @@
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.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;
+ }
+ }
+
+ 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(' ');
+ }
+ }
+
+ if (!nflag) {
+ putchar('\n');
+ }
+}
A test/echo => test/echo +29 -0
@@ 0,0 1,29 @@
+#!/bin/sh
+tool="echo"
+. "$HARNESS"
+
+should_handle_one_string() (
+ ct="$(../build/echo "this is a test string")"
+ [ "$ct" = "this is a test string" ]
+)
+
+should_handle_two_string() (
+ ct="$(../build/echo "this is a test string" "this second 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_handle_c_escape_char() (
+ ct="$(../build/echo -n "this is a \ctest string")"
+ [ "$ct" = "this is a " ]
+)
+
+runtests \
+ should_handle_one_string \
+ should_handle_two_string \
+ should_handle_tab_char \
+ should_handle_c_escape_char
M test/meson.build => test/meson.build +2 -2
@@ 1,15 1,15 @@
harness=join_paths(meson.current_source_dir(), 'harness.sh')
-
test_files = [
'basename',
'cat',
'chgrp',
- 'chown',
'chmod',
+ 'chown',
'cksum',
'cmp',
'comm',
'dirname',
+ 'echo',
'env',
'false',
'head',