| 1 | ---
 | 
| 2 | default_highlighter: oils-sh
 | 
| 3 | ---
 | 
| 4 | 
 | 
| 5 | Known Differences Between OSH and Other Shells
 | 
| 6 | ==============================================
 | 
| 7 | 
 | 
| 8 | This document is for **sophisticated shell users**.
 | 
| 9 | 
 | 
| 10 | You're unlikely to encounter these incompatibilities in everyday shell usage.
 | 
| 11 | If you do, there's almost always a **simple workaround**, like adding a space
 | 
| 12 | or a backslash.
 | 
| 13 | 
 | 
| 14 | OSH 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 | <!-- 
 | 
| 21 | TODO:
 | 
| 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 | 
 | 
| 36 | In other shells, `printf %d invalid_integer` prints `0` and a warning.  OSH
 | 
| 37 | gives 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 | 
 | 
| 43 | In shell, array locations are often dynamically parsed, and the index can have
 | 
| 44 | command subs, which execute arbitrary code.
 | 
| 45 | 
 | 
| 46 | For example, if you have `code='a[$(echo 42 | tee PWNED)]'`, shells will parse
 | 
| 47 | this 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 | 
 | 
| 57 | OSH disallows this by default.  If you want this behavior, you can turn on
 | 
| 58 | `shopt -s eval_unsafe_arith`.
 | 
| 59 | 
 | 
| 60 | Related: [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 | 
 | 
| 64 | This section describes differences related to [static
 | 
| 65 | parsing](http://www.oilshell.org/blog/2016/10/22.html).  OSH avoids the
 | 
| 66 | dynamic parsing of most shells.
 | 
| 67 | 
 | 
| 68 | (Note: This section should encompass all the failures from the [wild
 | 
| 69 | tests](http://oilshell.org/cross-ref.html?tag=wild-test#wild-test) and [spec
 | 
| 70 | tests](http://oilshell.org/cross-ref.html?tag=spec-test#spec-test).
 | 
| 71 | 
 | 
| 72 | ### Strings vs. Bare words in array indices
 | 
| 73 | 
 | 
| 74 | Strings should be quoted inside array indices:
 | 
| 75 | 
 | 
| 76 | No:
 | 
| 77 | 
 | 
| 78 |     "${SETUP_STATE[$err.cmd]}"
 | 
| 79 | 
 | 
| 80 | Yes:
 | 
| 81 | 
 | 
| 82 |     "${SETUP_STATE["$err.cmd"]}"
 | 
| 83 | 
 | 
| 84 | When unquoted, the period causes an ambiguity with respect to regular arrays
 | 
| 85 | vs. associative arrays.  See [Parsing Bash is
 | 
| 86 | Undecidable](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 | 
 | 
| 93 | You can have a subshell in a command sub, but it usually doesn't make sense.
 | 
| 94 | 
 | 
| 95 | In OSH you need a space after `$(`.  The characters `$((` always start an
 | 
| 96 | arith sub.
 | 
| 97 | 
 | 
| 98 | No:
 | 
| 99 | 
 | 
| 100 |     $((cd / && ls))
 | 
| 101 | 
 | 
| 102 | Yes:
 | 
| 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 | 
 | 
| 111 | The 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 | 
 | 
| 116 | In bash, the parsing of such expressions depends on `shopt -s extglob`.  In
 | 
| 117 | OSH, `shopt -s extglob` is accepted, but doesn't affect parsing.
 | 
| 118 | 
 | 
| 119 | ### Here doc terminators must be on their own line
 | 
| 120 | 
 | 
| 121 | Lines like `EOF]` or `EOF)` don't end here docs.  The delimiter must be on its
 | 
| 122 | own line.
 | 
| 123 | 
 | 
| 124 | No:
 | 
| 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 | 
 | 
| 135 | Yes:
 | 
| 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 | 
 | 
| 145 | Bash allows:
 | 
| 146 | 
 | 
| 147 |     a[1 + 2 * 3]=value
 | 
| 148 | 
 | 
| 149 | OSH only allows:
 | 
| 150 | 
 | 
| 151 |     a[1+2*3]=value
 | 
| 152 | 
 | 
| 153 | because it parses with limited lookahead.  The first line would result in the
 | 
| 154 | execution of a command named `a[1`.
 | 
| 155 | 
 | 
| 156 | ### break / continue / return are keywords, not builtins
 | 
| 157 | 
 | 
| 158 | This means that they aren't "dynamic":
 | 
| 159 | 
 | 
| 160 |     b=break
 | 
| 161 |     while true; do
 | 
| 162 |       $b  # doesn't break in OSH
 | 
| 163 |     done
 | 
| 164 | 
 | 
| 165 | Static 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 | 
 | 
| 171 | For example, `append` is a builtin in OSH, but not in `bash`.  Use `env append`
 | 
| 172 | or `/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 | 
 | 
| 179 | In contrast with builtins, **keywords** affect shell parsing.
 | 
| 180 | 
 | 
| 181 | For example, `func` is a keyword in OSH, but not in `bash`.  To run a command
 | 
| 182 | named `func`, use `command func arg1`.
 | 
| 183 | 
 | 
| 184 | Note that all shells have extensions that cause this issue.  For example, `[[`
 | 
| 185 | is a keyword in `bash` but not in POSIX shell.
 | 
| 186 | 
 | 
| 187 | ## Later Parsing Differences
 | 
| 188 | 
 | 
| 189 | These differences occur in subsequent stages of parsing, or in runtime parsing.
 | 
| 190 | 
 | 
| 191 | ### Brace expansion is all or nothing
 | 
| 192 | 
 | 
| 193 | No:
 | 
| 194 | 
 | 
| 195 |     {a,b}{        # what does the second { mean?
 | 
| 196 |     {a,b}{1...3}  # 3 dots instead of 2
 | 
| 197 | 
 | 
| 198 | Yes:
 | 
| 199 | 
 | 
| 200 |     {a,b}\{
 | 
| 201 |     {a,b}\{1...3\}
 | 
| 202 | 
 | 
| 203 | bash will do a **partial expansion** in the former cases, giving you `a{ b{`
 | 
| 204 | and `a{1...3} b{1...3}`.
 | 
| 205 | 
 | 
| 206 | OSH considers them syntax errors and aborts all brace expansion, giving you
 | 
| 207 | the same thing back: `{a,b}{` and `{a,b}{1...3}`.
 | 
| 208 | 
 | 
| 209 | ### Brackets should be escaped within Character Classes
 | 
| 210 | 
 | 
| 211 | Don't use ambiguous syntax for a character class consisting of a single bracket
 | 
| 212 | character.
 | 
| 213 | 
 | 
| 214 | No:
 | 
| 215 | 
 | 
| 216 |     echo [[]
 | 
| 217 |     echo []]
 | 
| 218 | 
 | 
| 219 | Yes:
 | 
| 220 | 
 | 
| 221 |     echo [\[]
 | 
| 222 |     echo [\]]
 | 
| 223 | 
 | 
| 224 | 
 | 
| 225 | The ambiguous syntax is allowed when we pass globs through to `libc`, but it's
 | 
| 226 | good practice to be explicit.
 | 
| 227 | 
 | 
| 228 | ### [[ -v var ]] doesn't allow expressions
 | 
| 229 | 
 | 
| 230 | In 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 | 
 | 
| 237 | Likewise for an associative array:
 | 
| 238 | 
 | 
| 239 |     declare -A assoc=([key]=value)
 | 
| 240 |     if [[ -v assoc['key'] ]]
 | 
| 241 |       echo 'exists'
 | 
| 242 |     fi  # => exists
 | 
| 243 | 
 | 
| 244 | OSH currently treats these expressions as a string, which means the status will
 | 
| 245 | be 1 (`false`).
 | 
| 246 | 
 | 
| 247 | Workaround:
 | 
| 248 | 
 | 
| 249 |     if [[ "${assoc['key']:+exists}" ]]; then
 | 
| 250 |       echo 'exists'
 | 
| 251 |     fi  # => exists
 | 
| 252 | 
 | 
| 253 | In 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 | 
 | 
| 264 | Most shells split the entries of arrays like `"$@"` and `"${a[@]}"` here:
 | 
| 265 | 
 | 
| 266 |     echo ${undef:-"$@"}
 | 
| 267 | 
 | 
| 268 | In OSH, omit the quotes if you want splitting:
 | 
| 269 | 
 | 
| 270 |     echo ${undef:-$@}
 | 
| 271 | 
 | 
| 272 | I 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 | 
 | 
| 276 | Even though there's a large common subset, OSH and bash have a different model
 | 
| 277 | for 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 | 
 | 
| 285 | In 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 | 
 | 
| 292 | For 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 | 
 | 
| 297 | In bash, you can tell the difference with `set -u`, but there's no difference
 | 
| 298 | in OSH.
 | 
| 299 | 
 | 
| 300 | ### Indexed and Associative arrays are distinct
 | 
| 301 | 
 | 
| 302 | Here 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 | 
 | 
| 312 | In 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 | 
 | 
| 317 | These are disallowed in OSH.
 | 
| 318 | 
 | 
| 319 | Notes:
 | 
| 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 | 
 | 
| 327 | The 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 | 
 | 
| 336 | This is because the special parsing of `x=$y` depends on the first word
 | 
| 337 | `declare`.
 | 
| 338 | 
 | 
| 339 | ### Args aren't split or globbed
 | 
| 340 | 
 | 
| 341 | In 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 | 
 | 
| 352 | In contrast, OSH doesn't split or glob args to assignment builtins.  This is
 | 
| 353 | more like the behavior of zsh.
 | 
| 354 | 
 | 
| 355 | ## Pipelines
 | 
| 356 | 
 | 
| 357 | ### Last pipeline part may run in shell process (zsh, bash `shopt -s lastpipe`)
 | 
| 358 | 
 | 
| 359 | In this pipeline, the builtin `read` is run in the shell process, not a child
 | 
| 360 | process:
 | 
| 361 | 
 | 
| 362 |     $ echo hi | read x
 | 
| 363 |     $ echo x=$x
 | 
| 364 |     x=hi  # empty in bash unless shopt -s lastpipe
 | 
| 365 | 
 | 
| 366 | If the last part is an external command, there is no difference:
 | 
| 367 | 
 | 
| 368 |     $ ls | wc -l
 | 
| 369 |     42
 | 
| 370 | 
 | 
| 371 | This is how zsh behaves, and how bash (sometimes) behaves with `shopt -s
 | 
| 372 | lastpipe`.
 | 
| 373 |   
 | 
| 374 | ### Pipelines can't be suspended with Ctrl-Z
 | 
| 375 | 
 | 
| 376 | Because the last part may be the current shell process, the entire pipeline
 | 
| 377 | can't be suspended.
 | 
| 378 | 
 | 
| 379 | OSH and zsh share this consequence of the `lastpipe` semantics.
 | 
| 380 | 
 | 
| 381 | In contrast, bash's `shopt -s lastpipe` is ignored in interactive shells.
 | 
| 382 | 
 | 
| 383 | ### `${PIPESTATUS[@]}` is only set after an actual pipeline
 | 
| 384 | 
 | 
| 385 | This makes it easier to check compound status codes without worrying about them
 | 
| 386 | being "clobbered".
 | 
| 387 | 
 | 
| 388 | Bash will set `${PIPESTATUS[@]}` on every command, regardless of whether its a
 | 
| 389 | pipeline.
 | 
| 390 | 
 | 
| 391 | ## More Differences at Runtime
 | 
| 392 | 
 | 
| 393 | ### Alias expansion
 | 
| 394 | 
 | 
| 395 | Almost 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 | 
 | 
| 402 | or
 | 
| 403 | 
 | 
| 404 |     alias a=
 | 
| 405 |     a (( var = 0 ))
 | 
| 406 | 
 | 
| 407 | Details on the OSH parsing model:
 | 
| 408 | 
 | 
| 409 | 1. Your code is statically parsed into an abstract syntax tree, which contains
 | 
| 410 |    many types of nodes.
 | 
| 411 | 2. `SimpleCommand` are the only ones that are further alias-expanded.
 | 
| 412 | 
 | 
| 413 | For example, these result in `SimpleCommand` nodes:
 | 
| 414 | 
 | 
| 415 | - `ls -l` 
 | 
| 416 | - `read -n 1` (normally a builtin)
 | 
| 417 | - `myfunc foo`
 | 
| 418 | 
 | 
| 419 | These don't:
 | 
| 420 | 
 | 
| 421 | - `x=42`
 | 
| 422 | - `declare -r x=42`
 | 
| 423 | - `break`, `continue`, `return`, `exit` — 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 | 
 | 
| 430 | That is, in OSH and mksh, something like `echo *.@(cc|h)` is an extended glob.
 | 
| 431 | But `echo $x`, where `$x` contains the pattern, is not.
 | 
| 432 | 
 | 
| 433 | For more details and differences, see the [Extended Glob
 | 
| 434 | section](word-language.html#extended-glob) of the Word Language doc.
 | 
| 435 | 
 | 
| 436 | ### Completion
 | 
| 437 | 
 | 
| 438 | The OSH completion API is mostly compatible with the bash completion API,
 | 
| 439 | except that it moves the **responsibility for quoting** out of plugins and onto
 | 
| 440 | the shell itself.  Plugins should return candidates as `argv` entries, not
 | 
| 441 | shell words.
 | 
| 442 | 
 | 
| 443 | See the [completion doc](completion.html) for details.
 | 
| 444 | 
 | 
| 445 | ## Interactive Features
 | 
| 446 | 
 | 
| 447 | ### History Substitution Language
 | 
| 448 | 
 | 
| 449 | The rules for history substitution like `!echo` are simpler.  There are no
 | 
| 450 | special cases to avoid clashes with `${!indirect}` and so forth.
 | 
| 451 | 
 | 
| 452 | TODO: Link to the history lexer.
 | 
| 453 | 
 | 
| 454 | <!--
 | 
| 455 | TODO: we want to make history more statically parsed.  Should test the ZSH
 | 
| 456 | parser.
 | 
| 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 | 
 | 
| 464 | External:
 | 
| 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 | 
 |