| 1 | ---
|
| 2 | default_highlighter: oils-sh
|
| 3 | ---
|
| 4 |
|
| 5 | OSH Quirks
|
| 6 | ==========
|
| 7 |
|
| 8 | This document describes corner cases in OSH.
|
| 9 |
|
| 10 | Related: [Known Differences](known-differences.html).
|
| 11 |
|
| 12 | <div id="toc">
|
| 13 | </div>
|
| 14 |
|
| 15 | ## For Bash Compatibility
|
| 16 |
|
| 17 | ### The meaning of `()` on the RHS
|
| 18 |
|
| 19 | In Oils, **values** are tagged with types like `Str` and `AssocArray`, as
|
| 20 | opposed to the *locations* of values (cells). The construct `()` on the RHS
|
| 21 | generates an instance of a distinct type `InitializerList`, which modifies the
|
| 22 | content of the LHS that it is assigned to.
|
| 23 |
|
| 24 | This statement binds an empty indexed array to the name `x`:
|
| 25 |
|
| 26 | x=() # indexed by integers
|
| 27 |
|
| 28 | This clears the content of the associative array.
|
| 29 |
|
| 30 | declare -A x=() # indexed by strings, because of -A
|
| 31 |
|
| 32 | These assign elements.
|
| 33 |
|
| 34 | declare -a x=(one two) # set elements
|
| 35 | declare -a x=(['k']=v) # set an element to the index $((k))
|
| 36 | declare -A x=(['k']=v) # set an element to the key 'k'
|
| 37 |
|
| 38 | This is not supported:
|
| 39 |
|
| 40 | declare -A x=(key value) # Error in osh
|
| 41 |
|
| 42 | When the variable does not exist and a type is not specified, the assignment
|
| 43 | creates an indexed array and applies the `InitializerList` to the created
|
| 44 | array.
|
| 45 |
|
| 46 | declare x=(one two) # creates an indexed array
|
| 47 | declare x=(['k']=v) # creates an indexed array
|
| 48 |
|
| 49 | **Quirk (osh <= 0.27.0)**: The construct `()` had an ambiguous type, which was
|
| 50 | either `BashArray` or `BashAssoc` depending on its content and the context.
|
| 51 | When it's clear from the context, `()` meant an empty **associative** array:
|
| 52 |
|
| 53 | declare -A x=() # indexed by strings, because of -A
|
| 54 |
|
| 55 | This was only applied when the array is empty. Otherwise the type was
|
| 56 | determined by the literal:
|
| 57 |
|
| 58 | declare x=(one two) # indexed array
|
| 59 | declare x=(['k']=v) # associative array
|
| 60 |
|
| 61 | These were redundant but OK:
|
| 62 |
|
| 63 | declare -a x=(one two) # indexed array
|
| 64 | declare -A x=(['k']=v) # associative array
|
| 65 |
|
| 66 | These produced errors:
|
| 67 |
|
| 68 | declare -A x=(one two) # inconsistent
|
| 69 | declare -a x=(['k']=v) # inconsistent
|
| 70 |
|
| 71 | ## Interactive Shell
|
| 72 |
|
| 73 | ### With job control, the DEBUG trap is disabled for the last part of a pipeline
|
| 74 |
|
| 75 | First, some background. These two shell features are fundamentally
|
| 76 | incompatible:
|
| 77 |
|
| 78 | - Job control: e.g. putting a pipeline in a process group, so it can be
|
| 79 | suspended and cancelled all at once.
|
| 80 | - `shopt -s lastpipe` semantics: the last part of a pipeline can (sometimes) be
|
| 81 | run in the current shell.
|
| 82 | - [OSH]($xref) uses it by default because it makes `echo hi | read myvar` work. So
|
| 83 | [OSH]($xref) is like [zsh]($xref), but unlike [bash](xref).
|
| 84 |
|
| 85 | As evidence of this incompatibility, note that:
|
| 86 |
|
| 87 | - [bash]($xref) simply ignores the `shopt -s lastpipe` setting in job control
|
| 88 | shells
|
| 89 | - [zsh]($xref) doesn't allow you to suspend some pipelines
|
| 90 |
|
| 91 | ---
|
| 92 |
|
| 93 | Now that we have that background, note that there's is a **third** feature that
|
| 94 | interacts: the `DEBUG` trap.
|
| 95 |
|
| 96 | [OSH]($xref) emulates the [bash]($xref) `DEBUG` trap, which runs before "leaf"
|
| 97 | commands like `echo hi`, `a=b`, etc.
|
| 98 |
|
| 99 | If we run this trap before the last part of a pipeline, **and** that part is
|
| 100 | run in the current shell (`lastpipe`), then the DEBUG trap makes an existing
|
| 101 | race condition worse.
|
| 102 |
|
| 103 | For example, in
|
| 104 |
|
| 105 | echo hi | cat
|
| 106 |
|
| 107 | there's nothing stopping `echo hi` from finishing before `cat` is even started,
|
| 108 | which means that `cat` can't join the process group of the leader.
|
| 109 |
|
| 110 | So we simply disable the `DEBUG` trap for the last part of the pipeline, but
|
| 111 | **only** when job control is enabled. This won't affect debugging batch
|
| 112 | programs.
|
| 113 |
|
| 114 | Related issues in other shells:
|
| 115 |
|
| 116 | - bash: <https://superuser.com/questions/1084406/chained-pipes-in-bash-throws-operation-not-permitted>
|
| 117 | - fish: <https://github.com/fish-shell/fish-shell/issues/7474>
|
| 118 |
|
| 119 | <!--
|
| 120 |
|
| 121 | ### errexit message and optimized subshells
|
| 122 |
|
| 123 | For all shells:
|
| 124 |
|
| 125 | sh -c 'date'
|
| 126 |
|
| 127 | gets rewritten into:
|
| 128 |
|
| 129 | sh -c 'exec date'
|
| 130 |
|
| 131 | That is, they **reuse the parent process**.
|
| 132 |
|
| 133 | Most shells don't print any diagnostic info when `errexit` is on. However, YSH
|
| 134 | does:
|
| 135 |
|
| 136 | osh -o errexit -c 'false'
|
| 137 | [ -c flag ]:1: fatal: Exiting with status 1
|
| 138 |
|
| 139 | `false` is a builtin rather than an external process, so YSH can print that
|
| 140 | message. But when running an external process, the message is lost:
|
| 141 |
|
| 142 | osh -o errexit -c 'env false'
|
| 143 | (silently fails with code 1)
|
| 144 | -->
|
| 145 |
|
| 146 | ## Related
|
| 147 |
|
| 148 | - The doc on [warts](warts.html) relates to YSH.
|
| 149 |
|