OILS / doc / known-differences.md View on Github | oilshell.org

475 lines, 297 significant
1---
2default_highlighter: oils-sh
3---
4
5Known Differences Between OSH and Other Shells
6==============================================
7
8This document is for **sophisticated shell users**.
9
10You're unlikely to encounter these incompatibilities in everyday shell usage.
11If you do, there's almost always a **simple workaround**, like adding a space
12or a backslash.
13
14OSH is meant to run all POSIX shell programs, and most bash programs.
15
16<!-- cmark.py expands this -->
17<div id="toc">
18</div>
19
20<!--
21TODO:
22
23- `` as comments in sandstorm
24 # This relates to comments being EOL or not
25
26- Pipelines
27 - PIPESTATUS only set when a pipeline is actually run.
28 - zsh-like lastpipe semantics.
29
30-->
31
32## Numbers and Arithmetic
33
34### printf '%d' and other numeric formats require a valid integer
35
36In other shells, `printf %d invalid_integer` prints `0` and a warning. OSH
37gives you a runtime error.
38
39<!-- TODO: Probably should be strict_arith -->
40
41### Dynamically parsed command subs disallowed unless `shopt -s eval_unsafe_arith`
42
43In shell, array locations are often dynamically parsed, and the index can have
44command subs, which execute arbitrary code.
45
46For example, if you have `code='a[$(echo 42 | tee PWNED)]'`, shells will parse
47this data and execute it in many situations:
48
49 echo $(( code )) # dynamic parsing and evaluation in bash, mksh, zsh
50
51 unset $code
52
53 printf -v $code hi
54
55 echo ${!code}
56
57OSH disallows this by default. If you want this behavior, you can turn on
58`shopt -s eval_unsafe_arith`.
59
60Related: [A 30-year-old security problem](https://www.oilshell.org/blog/2019/01/18.html#a-story-about-a-30-year-old-security-problem)
61
62## Static Parsing Differences
63
64This section describes differences related to [static
65parsing](http://www.oilshell.org/blog/2016/10/22.html). OSH avoids the
66dynamic parsing of most shells.
67
68(Note: This section should encompass all the failures from the [wild
69tests](http://oilshell.org/cross-ref.html?tag=wild-test#wild-test) and [spec
70tests](http://oilshell.org/cross-ref.html?tag=spec-test#spec-test).
71
72### Strings vs. Bare words in array indices
73
74Strings should be quoted inside array indices:
75
76No:
77
78 "${SETUP_STATE[$err.cmd]}"
79
80Yes:
81
82 "${SETUP_STATE["$err.cmd"]}"
83
84When unquoted, the period causes an ambiguity with respect to regular arrays
85vs. associative arrays. See [Parsing Bash is
86Undecidable](https://www.oilshell.org/blog/2016/10/20.html) (2016).
87
88- [OILS-ERR-101](error-catalog.html#oils-err-101) explains more ways to fix
89 this.
90
91### Subshell in command sub
92
93You can have a subshell in a command sub, but it usually doesn't make sense.
94
95In OSH you need a space after `$(`. The characters `$((` always start an
96arith sub.
97
98No:
99
100 $((cd / && ls))
101
102Yes:
103
104 $( (cd / && ls) ) # Valid but usually doesn't make sense.
105 $({ cd / && ls; }) # Use {} for grouping, not (). Note trailing ;
106 $(cd / && ls) # Even better
107
108
109### Extended glob vs. Negation of boolean expression
110
111The OSH parser distinguishes these two constructs with a space:
112
113- `[[ !(a == a) ]]` is an extended glob.
114- `[[ ! (a == a) ]]` is the negation of an equality test.
115
116In bash, the parsing of such expressions depends on `shopt -s extglob`. In
117OSH, `shopt -s extglob` is accepted, but doesn't affect parsing.
118
119### Here doc terminators must be on their own line
120
121Lines like `EOF]` or `EOF)` don't end here docs. The delimiter must be on its
122own line.
123
124No:
125
126 a=$(cat <<EOF
127 abc
128 EOF)
129
130 a=$(cat <<EOF
131 abc
132 EOF # this is not a comment; it makes the EOF delimiter invalid
133 )
134
135Yes:
136
137 a=$(cat <<EOF
138 abc
139 EOF
140 ) # this is actually a comment
141
142
143### Spaces aren't allowed in LHS indices
144
145Bash allows:
146
147 a[1 + 2 * 3]=value
148
149OSH only allows:
150
151 a[1+2*3]=value
152
153because it parses with limited lookahead. The first line would result in the
154execution of a command named `a[1`.
155
156### break / continue / return are keywords, not builtins
157
158This means that they aren't "dynamic":
159
160 b=break
161 while true; do
162 $b # doesn't break in OSH
163 done
164
165Static control flow will allow static analysis of shell scripts.
166
167(Test cases are in [spec/loop][]).
168
169### OSH has more builtins, which shadow external commands
170
171For example, `append` is a builtin in OSH, but not in `bash`. Use `env append`
172or `/path/to/append` if you want to run an external command.
173
174(Note that a user-defined proc `append` takes priority over the builtin
175`append`.)
176
177### OSH has more keywords, which shadow builtins, functions, and commands
178
179In contrast with builtins, **keywords** affect shell parsing.
180
181For example, `func` is a keyword in OSH, but not in `bash`. To run a command
182named `func`, use `command func arg1`.
183
184Note that all shells have extensions that cause this issue. For example, `[[`
185is a keyword in `bash` but not in POSIX shell.
186
187## Later Parsing Differences
188
189These differences occur in subsequent stages of parsing, or in runtime parsing.
190
191### Brace expansion is all or nothing
192
193No:
194
195 {a,b}{ # what does the second { mean?
196 {a,b}{1...3} # 3 dots instead of 2
197
198Yes:
199
200 {a,b}\{
201 {a,b}\{1...3\}
202
203bash will do a **partial expansion** in the former cases, giving you `a{ b{`
204and `a{1...3} b{1...3}`.
205
206OSH considers them syntax errors and aborts all brace expansion, giving you
207the same thing back: `{a,b}{` and `{a,b}{1...3}`.
208
209### Brackets should be escaped within Character Classes
210
211Don't use ambiguous syntax for a character class consisting of a single bracket
212character.
213
214No:
215
216 echo [[]
217 echo []]
218
219Yes:
220
221 echo [\[]
222 echo [\]]
223
224
225The ambiguous syntax is allowed when we pass globs through to `libc`, but it's
226good practice to be explicit.
227
228### [[ -v var ]] doesn't allow expressions
229
230In bash, you can use `[[` with `-v` to test whether an array contains an entry:
231
232 declare -a array=('' foo)
233 if [[ -v array[1] ]]; then
234 echo 'exists'
235 fi # => exists
236
237Likewise for an associative array:
238
239 declare -A assoc=([key]=value)
240 if [[ -v assoc['key'] ]]
241 echo 'exists'
242 fi # => exists
243
244OSH currently treats these expressions as a string, which means the status will
245be 1 (`false`).
246
247Workaround:
248
249 if [[ "${assoc['key']:+exists}" ]]; then
250 echo 'exists'
251 fi # => exists
252
253In ysh, you can use:
254
255 var d = { key: 42 }
256 if ('key' in d) {
257 echo 'exists'
258 } # => exists
259
260## Data Structures
261
262### Arrays aren't split inside ${}
263
264Most shells split the entries of arrays like `"$@"` and `"${a[@]}"` here:
265
266 echo ${undef:-"$@"}
267
268In OSH, omit the quotes if you want splitting:
269
270 echo ${undef:-$@}
271
272I think OSH is more consistent, but it disagrees with other shells.
273
274### Values are tagged with types, not locations (`declare -i -a -A`)
275
276Even though there's a large common subset, OSH and bash have a different model
277for typed data.
278
279- In OSH, **values** are tagged with types, which is how Python and JavaScript
280 work.
281- In bash, **cells** (locations for values) are tagged with types. Everything
282 is a string, but in certain contexts, strings are treated as integers or as
283 structured data.
284
285In particular,
286
287- The `-i` flag is a no-op in OSH. See [Shell Idioms > Remove Dynamic
288 Parsing](shell-idioms.html#remove-dynamic-parsing) for alternatives to `-i`.
289- The `-a` and `-A` flags behave differently. They pertain to the value, not
290 the location.
291
292For example, these two statements are different in bash, but the same in OSH:
293
294 declare -A assoc # unset cell that will LATER be an assoc array
295 declare -A assoc=() # empty associative array
296
297In bash, you can tell the difference with `set -u`, but there's no difference
298in OSH.
299
300### Indexed and Associative arrays are distinct
301
302Here is how you can create arrays in OSH, in a bash-compatible way:
303
304 local indexed=(foo bar)
305 local -a indexed=(foo bar) # -a is redundant
306 echo ${indexed[1]} # bar
307
308 local assoc=(['one']=1 ['two']=2)
309 local -A assoc=(['one']=1 ['two']=2) # -A is redundant
310 echo ${assoc['one']} # 1
311
312In bash, the distinction between the two is blurry, with cases like this:
313
314 local -A x=(foo bar) # -A disagrees with literal
315 local -a y=(['one']=1 ['two']=2) # -a disagrees with literal
316
317These are disallowed in OSH.
318
319Notes:
320
321- The `=` keyword is useful for gaining an understanding of the data model.
322- See the [Quirks](quirks.html) doc for details on how OSH uses this cleaner
323 model while staying compatible with bash.
324
325## Assignment builtins
326
327The assignment builtins are `export`, `readonly`, `local`, and
328`declare`/`typeset`. They're parsed in 2 ways:
329
330- Statically: to avoid word splitting in `declare x=$y` when `$y` contains
331 spaces. bash and other shells behave this way.
332- Dynamically: to handle expressions like `declare $1` where `$1` is `a=b`
333
334### `builtin declare x=$y` is a runtime error
335
336This is because the special parsing of `x=$y` depends on the first word
337`declare`.
338
339### Args aren't split or globbed
340
341In bash, you can do unusual things with args to assignment builtins:
342
343 vars='a=b x=y'
344 touch foo=bar.py spam=eggs.py
345
346 declare $vars *.py # assigns at least 4 variables
347 echo $a # b
348 echo $x # y
349 echo $foo # bar.py
350 echo $spam # eggs.py
351
352In contrast, OSH doesn't split or glob args to assignment builtins. This is
353more like the behavior of zsh.
354
355## Pipelines
356
357### Last pipeline part may run in shell process (zsh, bash `shopt -s lastpipe`)
358
359In this pipeline, the builtin `read` is run in the shell process, not a child
360process:
361
362 $ echo hi | read x
363 $ echo x=$x
364 x=hi # empty in bash unless shopt -s lastpipe
365
366If the last part is an external command, there is no difference:
367
368 $ ls | wc -l
369 42
370
371This is how zsh behaves, and how bash (sometimes) behaves with `shopt -s
372lastpipe`.
373
374### Pipelines can't be suspended with Ctrl-Z
375
376Because the last part may be the current shell process, the entire pipeline
377can't be suspended.
378
379OSH and zsh share this consequence of the `lastpipe` semantics.
380
381In contrast, bash's `shopt -s lastpipe` is ignored in interactive shells.
382
383### `${PIPESTATUS[@]}` is only set after an actual pipeline
384
385This makes it easier to check compound status codes without worrying about them
386being "clobbered".
387
388Bash will set `${PIPESTATUS[@]}` on every command, regardless of whether its a
389pipeline.
390
391## More Differences at Runtime
392
393### Alias expansion
394
395Almost all "real" aliases should work in OSH. But these don't work:
396
397 alias left='{'
398 left echo hi; }
399
400(cases #33-#34 in [spec/alias][])
401
402or
403
404 alias a=
405 a (( var = 0 ))
406
407Details on the OSH parsing model:
408
4091. Your code is statically parsed into an abstract syntax tree, which contains
410 many types of nodes.
4112. `SimpleCommand` are the only ones that are further alias-expanded.
412
413For example, these result in `SimpleCommand` nodes:
414
415- `ls -l`
416- `read -n 1` (normally a builtin)
417- `myfunc foo`
418
419These don't:
420
421- `x=42`
422- `declare -r x=42`
423- `break`, `continue`, `return`, `exit` &mdash; as explained above, these are
424 keywords and not builtins.
425- `{ echo one; echo two; }`
426- `for`, `while`, `case`, functions, etc.
427
428### Extended globs are more static like `mksh`, and have other differences
429
430That is, in OSH and mksh, something like `echo *.@(cc|h)` is an extended glob.
431But `echo $x`, where `$x` contains the pattern, is not.
432
433For more details and differences, see the [Extended Glob
434section](word-language.html#extended-glob) of the Word Language doc.
435
436### Completion
437
438The OSH completion API is mostly compatible with the bash completion API,
439except that it moves the **responsibility for quoting** out of plugins and onto
440the shell itself. Plugins should return candidates as `argv` entries, not
441shell words.
442
443See the [completion doc](completion.html) for details.
444
445## Interactive Features
446
447### History Substitution Language
448
449The rules for history substitution like `!echo` are simpler. There are no
450special cases to avoid clashes with `${!indirect}` and so forth.
451
452TODO: Link to the history lexer.
453
454<!--
455TODO: we want to make history more statically parsed. Should test the ZSH
456parser.
457-->
458
459## Links
460
461- [OSH Spec Tests](../test/spec.wwz/survey/osh.html) run shell snippets with OSH and other
462 shells to compare their behavior.
463
464External:
465
466- This list may seem long, but compare the list of differences in [Bash POSIX
467 Mode](https://www.gnu.org/software/bash/manual/html_node/Bash-POSIX-Mode.html).
468 That page tells you what `set -o posix` does in bash.
469
470
471[spec/command-sub]: ../test/spec.wwz/command-sub.html
472[spec/loop]: ../test/spec.wwz/loop.html
473[spec/alias]: ../test/spec.wwz/alias.html
474
475