diff options
| -rw-r--r-- | src/blog/2022-09-20-sh-2/sh-2.md | 394 | ||||
| -rw-r--r-- | src/blog/blog.md | 1 | ||||
| -rw-r--r-- | src/blog/feed.xml | 7 |
3 files changed, 402 insertions, 0 deletions
diff --git a/src/blog/2022-09-20-sh-2/sh-2.md b/src/blog/2022-09-20-sh-2/sh-2.md new file mode 100644 index 0000000..7f93d5f --- /dev/null +++ b/src/blog/2022-09-20-sh-2/sh-2.md | |||
| @@ -0,0 +1,394 @@ | |||
| 1 | # The man page reading club: sh(1) - part 2: commands and builtins | ||
| 2 | |||
| 3 | This is the second and last part of our exciting sh(1) manual page | ||
| 4 | read. This time we are going to learn about *commands* and *builtins*. | ||
| 5 | In case you have missed it, check out the [first part](../2022-09-13-sh-1) | ||
| 6 | where we dealt with the shell's grammar. | ||
| 7 | |||
| 8 | I'll spare you the fan fiction this time - let's go straight to the | ||
| 9 | technical part! | ||
| 10 | |||
| 11 | As usual, you can follow along at | ||
| 12 | [man.openbsd.org](https://man.openbsd.org/OpenBSD-7.1/sh) | ||
| 13 | |||
| 14 | ## Commands | ||
| 15 | |||
| 16 | The Commands section of the manual page starts like this: | ||
| 17 | |||
| 18 | ``` | ||
| 19 | The shell first expands any words that are not variable assignments or | ||
| 20 | redirections, with the first field being the command name and any | ||
| 21 | successive fields arguments to that command. It sets up redirections, if | ||
| 22 | any, and then expands variable assignments, if any. It then attempts to | ||
| 23 | run the command. | ||
| 24 | ``` | ||
| 25 | |||
| 26 | The next few paragraphs describe how the name of a command is | ||
| 27 | interpreted. There are two distinct cases: if the name contains | ||
| 28 | any slashes, it is considered as a path to a file; if it does not, | ||
| 29 | the shell tries to interpret it as a special builtin, as a shell | ||
| 30 | function, as a non-special builtin (the difference between these | ||
| 31 | two types of builtins will be explained later) or finally as the | ||
| 32 | name of an executable file (binary or script) to be looked for in | ||
| 33 | `$PATH`. | ||
| 34 | |||
| 35 | The meaning of this variable is explained in the `ENVIRONMENT` | ||
| 36 | section: | ||
| 37 | |||
| 38 | ``` | ||
| 39 | PATH Pathname to a colon separated list of directories used to search for | ||
| 40 | the location of executable files. A pathname of `.' represents the | ||
| 41 | current working directory. The default value of PATH on OpenBSD is: | ||
| 42 | |||
| 43 | /usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin | ||
| 44 | ``` | ||
| 45 | |||
| 46 | ### Grouping commands | ||
| 47 | |||
| 48 | The manual page continues with explaining how to group commands | ||
| 49 | together to create more complex commands. There are five ways to | ||
| 50 | create a list of commands, and their syntax is always of the form | ||
| 51 | |||
| 52 | ``` | ||
| 53 | command SEP command SEP ... | ||
| 54 | ``` | ||
| 55 | |||
| 56 | where `SEP` is one of the separators described below. | ||
| 57 | |||
| 58 | * *Sequential lists*: One or more commands separated by a semicolon `;` | ||
| 59 | are exectuted in order one after the other. | ||
| 60 | * *Asynchronous lists*: One or more commands separated by an ampersand `&` | ||
| 61 | are executed in parallel, each in a different subshell. | ||
| 62 | * *Pipelines*: Two or more commands separated by a pipe `|` are executed | ||
| 63 | in order, using the output of each command as input for the next one. | ||
| 64 | Together with I/O redirection, that we have seen last time, pipelines are | ||
| 65 | one of the "killer features" of UNIX that makes its shell such a powerful | ||
| 66 | language that it is still widely appreciated more than fifty years after | ||
| 67 | its introduction. | ||
| 68 | * *AND lists*: Two or more commands separated by a double ampersand `&&` | ||
| 69 | are executed in order, but a command is only run if the exit status of | ||
| 70 | the previous command was zero. | ||
| 71 | * *OR lists*: Two or more commands separated by a double pipe `||` | ||
| 72 | are executed in order, but a command is only run if the exit status of | ||
| 73 | the previous command was different from zero. | ||
| 74 | |||
| 75 | The AND and OR lists can be combined by using a mix of `&&` and | ||
| 76 | `||`. The two operators have the same precedence. | ||
| 77 | |||
| 78 | The exit status of a list of commands is equal to the exit status | ||
| 79 | of the last commands executed, except for asynchronous lists where | ||
| 80 | the exit status is always zero. For pipelines, the exit status can | ||
| 81 | be inverted by putting an exclamation mark `!` at the beginning of | ||
| 82 | the list. | ||
| 83 | |||
| 84 | Now that I think about it, I have mentioned the exit status of a | ||
| 85 | command a few times here and in the last episode, but I have never | ||
| 86 | explained what it is. Basically, every command concludes its | ||
| 87 | execution by returning a number (exit status), which may be zero | ||
| 88 | to indicate a succesful execution or anything different from zero | ||
| 89 | to indicate a failure. This will become even more relevant soon. | ||
| 90 | |||
| 91 | Finally, a list of commands can be treated as a single command by | ||
| 92 | enclosing it in parentheses or in braces: | ||
| 93 | |||
| 94 | ``` | ||
| 95 | Command lists, as described above, can be enclosed within `()' to have | ||
| 96 | them executed in a subshell, or within `{}' to have them executed in the | ||
| 97 | current environment: | ||
| 98 | |||
| 99 | (command ...) | ||
| 100 | { command ...; } | ||
| 101 | |||
| 102 | Any redirections specified after the closing bracket apply to all | ||
| 103 | commands within the brackets. An operator such as `;' or a newline are | ||
| 104 | needed to terminate a command list within curly braces. | ||
| 105 | ``` | ||
| 106 | |||
| 107 | ### Flow control | ||
| 108 | |||
| 109 | Much like any imperative programming language, the shell has some | ||
| 110 | constructs that allow controlling the flow of the execution. The | ||
| 111 | *for loop* is perhaps the most peculiar one. Its format is: | ||
| 112 | |||
| 113 | ``` | ||
| 114 | for name [in [word ...]] | ||
| 115 | do | ||
| 116 | command | ||
| 117 | ... | ||
| 118 | done | ||
| 119 | ``` | ||
| 120 | |||
| 121 | The commands are executed once for every item in the expansion of | ||
| 122 | `[word ...]` and every time the value of the variable `name` is set | ||
| 123 | to one of these items. (check [the last episode](../2022-09-13-sh-1) | ||
| 124 | for an explanation of text expansion). | ||
| 125 | |||
| 126 | *While loops* are perhaps more familiar to regular programmers: a | ||
| 127 | command called *condition* is run, and if its exit code is zero the | ||
| 128 | body of the while loop is executed, and so on. The format is | ||
| 129 | |||
| 130 | ``` | ||
| 131 | while condition | ||
| 132 | do | ||
| 133 | command | ||
| 134 | ... | ||
| 135 | done | ||
| 136 | ``` | ||
| 137 | |||
| 138 | There is an opposite construct with `until` in place of `while` | ||
| 139 | which executes the body as long as `condition` exits with non-zero | ||
| 140 | status. | ||
| 141 | |||
| 142 | A *case conditional* can be used to run commands depending on | ||
| 143 | something matching a pattern. The format is | ||
| 144 | |||
| 145 | ``` | ||
| 146 | case word in | ||
| 147 | (pattern [| pattern ...]) command;; | ||
| 148 | ... | ||
| 149 | esac | ||
| 150 | ``` | ||
| 151 | |||
| 152 | Where `pattern` can be expressed using the usual filename globbing | ||
| 153 | syntax that we briefly covered last time - see | ||
| 154 | [glob(7)](https://man.openbsd.org/OpenBSD-7.1/glob.7) for more | ||
| 155 | details. | ||
| 156 | |||
| 157 | As an example, this short code snippet tries to determine the type | ||
| 158 | of the file given as first argument from its extension: | ||
| 159 | |||
| 160 | ``` | ||
| 161 | case "$1" in | ||
| 162 | (*.txt) echo "Text file";; | ||
| 163 | (*.wav | *.mp3 | *.ogg) echo "Music file";; | ||
| 164 | (*) echo "Something else";; | ||
| 165 | esac | ||
| 166 | ``` | ||
| 167 | |||
| 168 | Note that double quotes around the `$1` to avoid file names with | ||
| 169 | spaces being considered as multiple words. | ||
| 170 | |||
| 171 | The *if conditional* is also a classic construct that programmers | ||
| 172 | are very familiar with. Its general format is | ||
| 173 | |||
| 174 | ``` | ||
| 175 | if conditional | ||
| 176 | then | ||
| 177 | command | ||
| 178 | ... | ||
| 179 | elif conditional | ||
| 180 | then | ||
| 181 | command | ||
| 182 | ... | ||
| 183 | else | ||
| 184 | command | ||
| 185 | ... | ||
| 186 | fi | ||
| 187 | ``` | ||
| 188 | |||
| 189 | Like for the `while` construct, `conditional` is a command that is | ||
| 190 | run and its exit status is evaluated. `elif` is just short for | ||
| 191 | "else, if...". | ||
| 192 | |||
| 193 | Finally, the shell also has functions, that are basically groups | ||
| 194 | of commands that can be given a name and executed when using that | ||
| 195 | name as a command. Their syntax may be simpler than you expect: | ||
| 196 | |||
| 197 | ``` | ||
| 198 | function() command-list | ||
| 199 | ``` | ||
| 200 | |||
| 201 | When defining functions it is common to write `command-list` in the | ||
| 202 | `{ command ; command ; ... ; }` format. Replacing the semicolons | ||
| 203 | with newlines we get the more familiar-looking structure | ||
| 204 | |||
| 205 | ``` | ||
| 206 | function() { | ||
| 207 | command | ||
| 208 | command | ||
| 209 | ... | ||
| 210 | } | ||
| 211 | ``` | ||
| 212 | |||
| 213 | ## Builtins | ||
| 214 | |||
| 215 | The builtins are listed in alphabetic order in the manual page, | ||
| 216 | which is very convenient when consulting it for reference, but it | ||
| 217 | is not the best choice for a top-to-bottom read. So I'll shuffle | ||
| 218 | them around and divide them into a few groups. I'll skip some stuff, | ||
| 219 | but I'll try to cover what is important for regular use. | ||
| 220 | |||
| 221 | But first, as promised at the beginning of the previous section, | ||
| 222 | we need to explain the difference between "special" and regular | ||
| 223 | builtins. | ||
| 224 | |||
| 225 | ``` | ||
| 226 | A number of built-ins are special in that a syntax error can cause a | ||
| 227 | running shell to abort, and, after the built-in completes, variable | ||
| 228 | assignments remain in the current environment. The following built-ins | ||
| 229 | are special: ., :, break, continue, eval, exec, exit, export, readonly, | ||
| 230 | return, set, shift, times, trap, and unset. | ||
| 231 | ``` | ||
| 232 | |||
| 233 | ### More programming features | ||
| 234 | |||
| 235 | As we have seen, the shell language includes some classical programming | ||
| 236 | constructs, like `if` and `while`. There are more builtins that can be | ||
| 237 | helpful these constructs: for example `true` and `false` are builtins | ||
| 238 | that do nothing and return a zero and a non-zero value respectively, | ||
| 239 | thus acting as sort of "boolean variables". | ||
| 240 | |||
| 241 | The builtins `break` and `continue`, used inside a loop of any kind, | ||
| 242 | behave exactly as in C. The builtin `return` is used to exit the current | ||
| 243 | function. An exit code may be specified as a parameter, to indicate | ||
| 244 | success (0) or failure (any other number). | ||
| 245 | |||
| 246 | ### Variables | ||
| 247 | |||
| 248 | The builtin `read` can be used to get input from the user - or | ||
| 249 | indeed from anywhere else, thanks to redirection: | ||
| 250 | |||
| 251 | ``` | ||
| 252 | read [-r] name ... | ||
| 253 | Read a line from standard input. The line is split into fields, with | ||
| 254 | each field assigned to a variable, name, in turn (first field | ||
| 255 | assigned to first variable, and so on). If there are more fields | ||
| 256 | than variables, the last variable will contain all the remaining | ||
| 257 | fields. If there are more variables than fields, the remaining | ||
| 258 | variables are set to empty strings. A backslash in the input line | ||
| 259 | causes the shell to prompt for further input. | ||
| 260 | |||
| 261 | The options to the read command are as follows: | ||
| 262 | |||
| 263 | -r Ignore backslash sequences. | ||
| 264 | ``` | ||
| 265 | |||
| 266 | As an example of reading from something other than standard input, | ||
| 267 | this short script takes a filename as an argument and prints each | ||
| 268 | line of the file preceded by its line number: | ||
| 269 | |||
| 270 | ``` | ||
| 271 | i=0 | ||
| 272 | while read line | ||
| 273 | do | ||
| 274 | i=$((i+1)) | ||
| 275 | echo $i: $line | ||
| 276 | done < $1 | ||
| 277 | ``` | ||
| 278 | |||
| 279 | Notice that the redirector `< $1` is placed at the end of the `while` | ||
| 280 | commend, after then closing `done`. | ||
| 281 | |||
| 282 | The builtins `export` and `readonly` deal with permissions: the | ||
| 283 | first is used to make a variable visible to all subsequently ran | ||
| 284 | commands (by default it is not), while the latter is used to make | ||
| 285 | a variable unchangeable. The syntax is the same for both: | ||
| 286 | |||
| 287 | ``` | ||
| 288 | command [-p] name[=value] | ||
| 289 | ``` | ||
| 290 | |||
| 291 | If `=value` is given, the value is assigned to the variable before | ||
| 292 | changing the permissions. The option `-p` is used to list out all | ||
| 293 | the variables that are currently exported or set as read-only. | ||
| 294 | |||
| 295 | ### Running commands | ||
| 296 | |||
| 297 | If you want to run the commands contained in `file`, you can do so | ||
| 298 | by using `. file` (the single dot is a builtin). For example you | ||
| 299 | can list some commands that you want to run at the beginning of | ||
| 300 | each shell session (e.g. aliases, see the next section) and run | ||
| 301 | them with just one command. Many other shells, such as ksh, run | ||
| 302 | certain files like `.profile` at startup, but sh does not. | ||
| 303 | |||
| 304 | If the commands you want to run are saved in variables or other | ||
| 305 | parameters you can use `eval`. For example, the following script | ||
| 306 | takes a command and its arguments as parameters, runs them and | ||
| 307 | returns a different message depending on the exit code: | ||
| 308 | |||
| 309 | ``` | ||
| 310 | if eval $@ | ||
| 311 | then | ||
| 312 | echo "The command $@ ran happily" | ||
| 313 | else | ||
| 314 | echo "Oh no! Something went wrong!" | ||
| 315 | fi | ||
| 316 | ``` | ||
| 317 | |||
| 318 | ### Aliases | ||
| 319 | |||
| 320 | Aliases provide a nice shortcut sometimes, for example for shortening | ||
| 321 | a long command name or for adding a certain set of options by | ||
| 322 | default. | ||
| 323 | |||
| 324 | Using `alias name=value` makes it so every time `name` is read by | ||
| 325 | the shell as a command (i.e. not when it is an argument) it is | ||
| 326 | replaced by `value`. For example using `alias off='shutdown -p now'` | ||
| 327 | can be used to easily call the `shutdown` command with the common | ||
| 328 | option `-p now` - check out [an older blog entry](../2022-07-07-shutdown) | ||
| 329 | to learn about this surprisingly feature-rich command! | ||
| 330 | |||
| 331 | Using just `alias name` tells you the value of the corresponding alias, | ||
| 332 | if it is set. Using `alias` with no argument returns a list of all | ||
| 333 | currently set aliases. Contrary to variables, aliases are visible in | ||
| 334 | every subshell. | ||
| 335 | |||
| 336 | Finally, `unalias name` can be used to unset the corresponding | ||
| 337 | alias; `unalias -a` unsets all currently set aliases. | ||
| 338 | |||
| 339 | ### Moving around directories | ||
| 340 | |||
| 341 | Next (a meaningless word, since we are going in our own completely | ||
| 342 | arbitrary order) we have `cd` and `pwd`, which can be used to move around | ||
| 343 | in the directory tree. | ||
| 344 | |||
| 345 | `pwd` simply prints the current path - it is short for "Print Working | ||
| 346 | Directory". The working directory is where files are looked for by | ||
| 347 | the shell, for example when used as arguments for commands. If a | ||
| 348 | file is not in the current working directory, its full path has to | ||
| 349 | be specified in order to refer to it. | ||
| 350 | |||
| 351 | The working directory can be changed with `cd path/to/new/directory`. | ||
| 352 | If the path is not specified, it defaults to `$HOME`, the home | ||
| 353 | directory of the current user. The path can also be a single dash | ||
| 354 | `-`, meaning "return to the previous working directory". Finally, | ||
| 355 | if the path does not start with a slash and is not found relatively | ||
| 356 | to the current working directory, the variable `CDPATH`, which | ||
| 357 | should contain a colon-separated list of directories, is read to | ||
| 358 | try and find the new directory starting from there. | ||
| 359 | |||
| 360 | ### Jobs | ||
| 361 | |||
| 362 | The builtins `jobs`, `kill`, `bg` and `fg` can be used to manage multiple | ||
| 363 | jobs running in the same shell. For example you can can run a command in | ||
| 364 | the background with `command &`, and later kill it with `kill [id]` or | ||
| 365 | bring it to the foreground with `fg [id]` (the `id` of the command will | ||
| 366 | be printed by the shell when you run `command &`). | ||
| 367 | |||
| 368 | I wanted to write something more about this, but I found the man | ||
| 369 | page for sh a bit lacking. I had to rely on other resources, such | ||
| 370 | as the manual page of [ksh(1)](https://man.openbsd.org/OpenBSD-7.1/ksh). | ||
| 371 | I think I'll postpone *job control* to another entry. Stay tuned! | ||
| 372 | |||
| 373 | ### And finally... | ||
| 374 | |||
| 375 | ``` | ||
| 376 | exit [n] | ||
| 377 | Exit the shell with exit status n, or that of the last command executed. | ||
| 378 | ``` | ||
| 379 | |||
| 380 | ## Conclusion | ||
| 381 | |||
| 382 | I have skipped a few sections of the man page and many of the | ||
| 383 | builtins, but I am happy with the result and I think we can end it | ||
| 384 | here. After all, if I did not make any selection at all for these | ||
| 385 | "reading club" entries, you could just read the manual page yourself, | ||
| 386 | so what would the point be? | ||
| 387 | |||
| 388 | I am not sure what I am going to cover in the next episode. On the one | ||
| 389 | hand I should alternate between shorter pages and longer ones, mainly | ||
| 390 | to avoid burning out by taking on too many huge projects. But on the | ||
| 391 | other hand long pages are often more interesting. | ||
| 392 | |||
| 393 | Anyway, I hope you enjoyed this long double-post and that you may have | ||
| 394 | learnt something new. See you next time! | ||
diff --git a/src/blog/blog.md b/src/blog/blog.md index 2a02644..a1227f7 100644 --- a/src/blog/blog.md +++ b/src/blog/blog.md | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | [RSS Feed](feed.xml) | 3 | [RSS Feed](feed.xml) |
| 4 | 4 | ||
| 5 | * 2022-09-20 [The man page reading club: sh(1) - part 2: commands and builtins](2022-09-20-sh-2) | ||
| 5 | * 2022-09-13 [The man page reading club: sh(1) - part 1: shell grammar](2022-09-13-sh-1) | 6 | * 2022-09-13 [The man page reading club: sh(1) - part 1: shell grammar](2022-09-13-sh-1) |
| 6 | * 2022-09-10 [Long live netbooks!](2022-09-10-netbooks) | 7 | * 2022-09-10 [Long live netbooks!](2022-09-10-netbooks) |
| 7 | * 2022-09-05 [Pipe man into col -b to get rid of \^H](2022-09-05-man-col) | 8 | * 2022-09-05 [Pipe man into col -b to get rid of \^H](2022-09-05-man-col) |
diff --git a/src/blog/feed.xml b/src/blog/feed.xml index 3b718d2..d8e7876 100644 --- a/src/blog/feed.xml +++ b/src/blog/feed.xml | |||
| @@ -9,6 +9,13 @@ Thoughts about software, computers and whatever I feel like sharing | |||
| 9 | </description> | 9 | </description> |
| 10 | 10 | ||
| 11 | <item> | 11 | <item> |
| 12 | <title>The man page reading club: sh(1) - part 2: commands and builtins</title> | ||
| 13 | <link>https://sebastiano.tronto.net/blog/2022-09-20-sh-2</link> | ||
| 14 | <description>The man page reading club: sh(1) - part 2: commands and builtins</description> | ||
| 15 | <pubDate>2022-09-20</pubDate> | ||
| 16 | </item> | ||
| 17 | |||
| 18 | <item> | ||
| 12 | <title>The man page reading club: sh(1) - part 1: shell grammar</title> | 19 | <title>The man page reading club: sh(1) - part 1: shell grammar</title> |
| 13 | <link>https://sebastiano.tronto.net/blog/2022-09-13-sh-1</link> | 20 | <link>https://sebastiano.tronto.net/blog/2022-09-13-sh-1</link> |
| 14 | <description>The man page reading club: sh(1) - part 1: shell grammar</description> | 21 | <description>The man page reading club: sh(1) - part 1: shell grammar</description> |
