summaryrefslogtreecommitdiff
path: root/docs/handbook/corebinutils/echo.md
blob: da7df16cec97c83322f08b720388402947589a2e (plain)
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
# echo — Write Arguments to Standard Output

## Overview

`echo` writes its arguments to standard output, separated by spaces, followed
by a newline. It is intentionally minimal — the FreeBSD/BSD implementation
does not support GNU-style `-e` escape processing.

**Source**: `echo/echo.c` (single file)
**Origin**: BSD 4.4, University of California, Berkeley
**License**: BSD-3-Clause

## Synopsis

```
echo [-n] [string ...]
```

## Options

| Flag | Description |
|------|-------------|
| `-n` | Suppress trailing newline |

Only a leading `-n` is recognized as an option. Any other arguments
(including `--`) are treated as literal strings and printed.

## Source Analysis

### Functions

| Function | Purpose |
|----------|---------|
| `main()` | Parse arguments and write output |
| `write_all()` | Retry-safe `write(2)` loop handling `EINTR` |
| `warn_errno()` | Error reporting to stderr |
| `trim_trailing_backslash_c()` | Check if final argument ends with `\c` |

### Option Processing

`echo` does NOT use `getopt(3)`. It manually checks for `-n`:

```c
int main(int argc, char *argv[])
{
    bool suppress_newline = false;

    argv++;  /* Skip program name */

    /* Only leading -n flags are consumed */
    while (*argv && strcmp(*argv, "-n") == 0) {
        suppress_newline = true;
        argv++;
    }

    /* Everything else is literal output */
}
```

### The `\c` Convention

If the **last** argument ends with `\c`, the trailing newline is suppressed
and the `\c` itself is not printed:

```c
static bool
trim_trailing_backslash_c(const char *arg, size_t *len)
{
    if (*len >= 2 && arg[*len - 2] == '\\' && arg[*len - 1] == 'c') {
        *len -= 2;
        return true;  /* Suppress newline */
    }
    return false;
}
```

### I/O Strategy

Instead of `printf` or `writev`, echo uses a `write(2)` loop:

```c
static int
write_all(int fd, const void *buf, size_t count)
{
    const char *p = buf;
    ssize_t n;

    while (count > 0) {
        n = write(fd, p, count);
        if (n < 0) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        p += n;
        count -= n;
    }
    return 0;
}
```

This avoids `IOV_MAX` limitations that would apply with `writev(2)` when
there are many arguments.

### Key Behaviors

| Input | Output | Notes |
|-------|--------|-------|
| `echo hello` | `hello\n` | Basic usage |
| `echo -n hello` | `hello` | No trailing newline |
| `echo -n -n hello` | `hello` | Multiple `-n` consumed |
| `echo -- hello` | `-- hello\n` | `--` is NOT end-of-options |
| `echo -e hello` | `-e hello\n` | `-e` is NOT recognized |
| `echo "hello\c"` | `hello` | `\c` suppresses newline |
| `echo ""` | `\n` | Empty string → just newline |

## Portability Notes

- **BSD echo** (this implementation): Only `-n` and trailing `\c`
- **GNU echo**: Supports `-e` for escape sequences (`\n`, `\t`, etc.)
  and `-E` to disable them
- **POSIX echo**: Behavior of `-n` and backslash sequences is
  implementation-defined
- **Shell built-in**: Most shells have a built-in `echo` that may differ
  from the external command

## System Calls Used

| Syscall | Purpose |
|---------|---------|
| `write(2)` | All output to stdout |

## Examples

```sh
# Simple output
echo Hello, World!

# No trailing newline
echo -n "prompt> "

# Literal dash-n (only leading -n is recognized)
echo "The flag is -n"

# Multiple arguments
echo one two three
# → "one two three"

# Suppress newline with \c
echo "no newline\c"
```

## Exit Codes

| Code | Meaning |
|------|---------|
| 0    | Success |
| 1    | Write error |