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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
|
# The man page reading club: sh(1) - part 2: commands and builtins
*This post is part of a [series](../../series)*
This is the second and last part of our exciting sh(1) manual page
read. This time we are going to learn about *commands* and *builtins*.
In case you have missed it, check out the [first part](../2022-09-13-sh-1)
where we dealt with the shell's grammar.
I'll spare you the fan fiction this time - let's go straight to the
technical part!
As usual, you can follow along at
[man.openbsd.org](https://man.openbsd.org/OpenBSD-7.1/sh)
## Commands
The Commands section of the manual page starts like this:
```
The shell first expands any words that are not variable assignments or
redirections, with the first field being the command name and any
successive fields arguments to that command. It sets up redirections, if
any, and then expands variable assignments, if any. It then attempts to
run the command.
```
The next few paragraphs describe how the name of a command is
interpreted. There are two distinct cases: if the name contains
any slashes, it is considered as a path to a file; if it does not,
the shell tries to interpret it as a special builtin, as a shell
function, as a non-special builtin (the difference between these
two types of builtins will be explained later) or finally as the
name of an executable file (binary or script) to be looked for in
`$PATH`.
The meaning of this variable is explained in the `ENVIRONMENT`
section:
```
PATH Pathname to a colon separated list of directories used to search for
the location of executable files. A pathname of `.' represents the
current working directory. The default value of PATH on OpenBSD is:
/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin
```
### Grouping commands
The manual page continues with explaining how to group commands
together to create more complex commands. There are five ways to
create a list of commands, and their syntax is always of the form
```
command SEP command SEP ...
```
where `SEP` is one of the separators described below.
* *Sequential lists*: One or more commands separated by a semicolon `;`
are exectuted in order one after the other.
* *Asynchronous lists*: One or more commands separated by an ampersand `&`
are executed in parallel, each in a different subshell.
* *Pipelines*: Two or more commands separated by a pipe `|` are executed
in order, using the output of each command as input for the next one.
Together with I/O redirection, that we have seen last time, pipelines are
one of the "killer features" of UNIX that makes its shell such a powerful
language that it is still widely appreciated more than fifty years after
its introduction.
* *AND lists*: Two or more commands separated by a double ampersand `&&`
are executed in order, but a command is only run if the exit status of
the previous command was zero.
* *OR lists*: Two or more commands separated by a double pipe `||`
are executed in order, but a command is only run if the exit status of
the previous command was different from zero.
The AND and OR lists can be combined by using a mix of `&&` and
`||`. The two operators have the same precedence.
The exit status of a list of commands is equal to the exit status
of the last commands executed, except for asynchronous lists where
the exit status is always zero. For pipelines, the exit status can
be inverted by putting an exclamation mark `!` at the beginning of
the list.
Now that I think about it, I have mentioned the exit status of a
command a few times here and in the last episode, but I have never
explained what it is. Basically, every command concludes its
execution by returning a number (exit status), which may be zero
to indicate a succesful execution or anything different from zero
to indicate a failure. This will become even more relevant soon.
Finally, a list of commands can be treated as a single command by
enclosing it in parentheses or in braces:
```
Command lists, as described above, can be enclosed within `()' to have
them executed in a subshell, or within `{}' to have them executed in the
current environment:
(command ...)
{ command ...; }
Any redirections specified after the closing bracket apply to all
commands within the brackets. An operator such as `;' or a newline are
needed to terminate a command list within curly braces.
```
### Flow control
Much like any imperative programming language, the shell has some
constructs that allow controlling the flow of the execution. The
*for loop* is perhaps the most peculiar one. Its format is:
```
for name [in [word ...]]
do
command
...
done
```
The commands are executed once for every item in the expansion of
`[word ...]` and every time the value of the variable `name` is set
to one of these items. (check [the last episode](../2022-09-13-sh-1)
for an explanation of text expansion).
*While loops* are perhaps more familiar to regular programmers: a
command called *condition* is run, and if its exit code is zero the
body of the while loop is executed, and so on. The format is
```
while condition
do
command
...
done
```
There is an opposite construct with `until` in place of `while`
which executes the body as long as `condition` exits with non-zero
status.
A *case conditional* can be used to run commands depending on
something matching a pattern. The format is
```
case word in
(pattern [| pattern ...]) command;;
...
esac
```
Where `pattern` can be expressed using the usual filename globbing
syntax that we briefly covered last time - see
[glob(7)](https://man.openbsd.org/OpenBSD-7.1/glob.7) for more
details.
As an example, this short code snippet tries to determine the type
of the file given as first argument from its extension:
```
case "$1" in
(*.txt) echo "Text file";;
(*.wav | *.mp3 | *.ogg) echo "Music file";;
(*) echo "Something else";;
esac
```
Note that double quotes around the `$1` to avoid file names with
spaces being considered as multiple words.
The *if conditional* is also a classic construct that programmers
are very familiar with. Its general format is
```
if conditional
then
command
...
elif conditional
then
command
...
else
command
...
fi
```
Like for the `while` construct, `conditional` is a command that is
run and its exit status is evaluated. `elif` is just short for
"else, if...".
Finally, the shell also has functions, that are basically groups
of commands that can be given a name and executed when using that
name as a command. Their syntax may be simpler than you expect:
```
function() command-list
```
When defining functions it is common to write `command-list` in the
`{ command ; command ; ... ; }` format. Replacing the semicolons
with newlines we get the more familiar-looking structure
```
function() {
command
command
...
}
```
## Builtins
The builtins are listed in alphabetic order in the manual page,
which is very convenient when consulting it for reference, but it
is not the best choice for a top-to-bottom read. So I'll shuffle
them around and divide them into a few groups. I'll skip some stuff,
but I'll try to cover what is important for regular use.
But first, as promised at the beginning of the previous section,
we need to explain the difference between "special" and regular
builtins.
```
A number of built-ins are special in that a syntax error can cause a
running shell to abort, and, after the built-in completes, variable
assignments remain in the current environment. The following built-ins
are special: ., :, break, continue, eval, exec, exit, export, readonly,
return, set, shift, times, trap, and unset.
```
### More programming features
As we have seen, the shell language includes some classical programming
constructs, like `if` and `while`. There are more builtins that can be
helpful these constructs: for example `true` and `false` are builtins
that do nothing and return a zero and a non-zero value respectively,
thus acting as sort of "boolean variables".
The builtins `break` and `continue`, used inside a loop of any kind,
behave exactly as in C. The builtin `return` is used to exit the current
function. An exit code may be specified as a parameter, to indicate
success (0) or failure (any other number).
### Variables
The builtin `read` can be used to get input from the user - or
indeed from anywhere else, thanks to redirection:
```
read [-r] name ...
Read a line from standard input. The line is split into fields, with
each field assigned to a variable, name, in turn (first field
assigned to first variable, and so on). If there are more fields
than variables, the last variable will contain all the remaining
fields. If there are more variables than fields, the remaining
variables are set to empty strings. A backslash in the input line
causes the shell to prompt for further input.
The options to the read command are as follows:
-r Ignore backslash sequences.
```
As an example of reading from something other than standard input,
this short script takes a filename as an argument and prints each
line of the file preceded by its line number:
```
i=0
while read line
do
i=$((i+1))
echo $i: $line
done < $1
```
Notice that the redirector `< $1` is placed at the end of the `while`
commend, after then closing `done`.
The builtins `export` and `readonly` deal with permissions: the
first is used to make a variable visible to all subsequently ran
commands (by default it is not), while the latter is used to make
a variable unchangeable. The syntax is the same for both:
```
command [-p] name[=value]
```
If `=value` is given, the value is assigned to the variable before
changing the permissions. The option `-p` is used to list out all
the variables that are currently exported or set as read-only.
### Running commands
If you want to run the commands contained in `file`, you can do so
by using `. file` (the single dot is a builtin). For example you
can list some commands that you want to run at the beginning of
each shell session (e.g. aliases, see the next section) and run
them with just one command. Many other shells, such as ksh, run
certain files like `.profile` at startup, but sh does not.
If the commands you want to run are saved in variables or other
parameters you can use `eval`. For example, the following script
takes a command and its arguments as parameters, runs them and
returns a different message depending on the exit code:
```
if eval $@
then
echo "The command $@ ran happily"
else
echo "Oh no! Something went wrong!"
fi
```
### Aliases
Aliases provide a nice shortcut sometimes, for example for shortening
a long command name or for adding a certain set of options by
default.
Using `alias name=value` makes it so every time `name` is read by
the shell as a command (i.e. not when it is an argument) it is
replaced by `value`. For example using `alias off='shutdown -p now'`
can be used to easily call the `shutdown` command with the common
option `-p now` - check out [an older blog entry](../2022-07-07-shutdown)
to learn about this surprisingly feature-rich command!
Using just `alias name` tells you the value of the corresponding alias,
if it is set. Using `alias` with no argument returns a list of all
currently set aliases. Contrary to variables, aliases are visible in
every subshell.
Finally, `unalias name` can be used to unset the corresponding
alias; `unalias -a` unsets all currently set aliases.
### Moving around directories
Next (a meaningless word, since we are going in our own completely
arbitrary order) we have `cd` and `pwd`, which can be used to move around
in the directory tree.
`pwd` simply prints the current path - it is short for "Print Working
Directory". The working directory is where files are looked for by
the shell, for example when used as arguments for commands. If a
file is not in the current working directory, its full path has to
be specified in order to refer to it.
The working directory can be changed with `cd path/to/new/directory`.
If the path is not specified, it defaults to `$HOME`, the home
directory of the current user. The path can also be a single dash
`-`, meaning "return to the previous working directory". Finally,
if the path does not start with a slash and is not found relatively
to the current working directory, the variable `CDPATH`, which
should contain a colon-separated list of directories, is read to
try and find the new directory starting from there.
### Jobs
The builtins `jobs`, `kill`, `bg` and `fg` can be used to manage multiple
jobs running in the same shell. For example you can can run a command in
the background with `command &`, and later kill it with `kill [id]` or
bring it to the foreground with `fg [id]` (the `id` of the command will
be printed by the shell when you run `command &`).
I wanted to write something more about this, but I found the man
page for sh a bit lacking. I had to rely on other resources, such
as the manual page of [ksh(1)](https://man.openbsd.org/OpenBSD-7.1/ksh).
I think I'll postpone *job control* to another entry. Stay tuned!
*Update: [here](../2023-02-25-job-control) is the post on job control.*
### And finally...
```
exit [n]
Exit the shell with exit status n, or that of the last command executed.
```
## Conclusion
I have skipped a few sections of the man page and many of the
builtins, but I am happy with the result and I think we can end it
here. After all, if I did not make any selection at all for these
"reading club" entries, you could just read the manual page yourself,
so what would the point be?
I am not sure what I am going to cover in the next episode. On the one
hand I should alternate between shorter pages and longer ones, mainly
to avoid burning out by taking on too many huge projects. But on the
other hand long pages are often more interesting.
Anyway, I hope you enjoyed this long double-post and that you may have
learnt something new. See you next time!
*Next in the series: [tetris(6)](../2022-10-01-tetris)*
|