| 1 | ---
|
| 2 | default_highlighter: oils-sh
|
| 3 | ---
|
| 4 |
|
| 5 | Shell Language Idioms
|
| 6 | =====================
|
| 7 |
|
| 8 | These are like the [YSH vs. Shell Idioms](idioms.html), but the advice also
|
| 9 | applies to other Unix shells.
|
| 10 |
|
| 11 | <div id="toc">
|
| 12 | </div>
|
| 13 |
|
| 14 | ## Style
|
| 15 |
|
| 16 | ### Use Only `"$@"`
|
| 17 |
|
| 18 | There's no reason to use anything but `"$@"`. All the other forms like `$*`
|
| 19 | can be disallowed, because if you want to join to a string, you can write:
|
| 20 |
|
| 21 | joined_str="$@"
|
| 22 |
|
| 23 | The same advice applies to arrays. You can always use `"${myarray[@]}"`; you
|
| 24 | never need to use `${myarray[*]}` or any other form.
|
| 25 |
|
| 26 | Related: [Thirteen Incorrect Ways and Two Awkward Ways to Use
|
| 27 | Arrays](http://www.oilshell.org/blog/2016/11/06.html)
|
| 28 |
|
| 29 | ### Prefer `test` to `[`
|
| 30 |
|
| 31 | Idiomatic OSH code doesn't use "puns".
|
| 32 |
|
| 33 | No:
|
| 34 |
|
| 35 | [ -d /tmp ]
|
| 36 |
|
| 37 | Yes:
|
| 38 |
|
| 39 | test -d /tmp
|
| 40 |
|
| 41 | The [simple_test_builtin](ref/chap-option.html#ysh:all) option enforces this.
|
| 42 |
|
| 43 | ## Use Statically Parsed Language Constructs
|
| 44 |
|
| 45 | Static parsing is one of the [syntactic concepts](syntactic-concepts.html). It
|
| 46 | leads to better error messages, earlier error messages, and lets tools
|
| 47 | understand your code.
|
| 48 |
|
| 49 | ### `test` Should Only Have 2 or 3 Arguments
|
| 50 |
|
| 51 | In POSIX, the `test` builtin has a lot of unnecessary flexibility, which leads
|
| 52 | to bugs.
|
| 53 |
|
| 54 | See [Problems With the test Builtin: What Does -a
|
| 55 | Mean?](//www.oilshell.org/blog/2017/08/31.html)
|
| 56 |
|
| 57 | No:
|
| 58 |
|
| 59 | test ! -d /tmp
|
| 60 | test -d /tmp -a -d /tmp/foo
|
| 61 |
|
| 62 | Yes:
|
| 63 |
|
| 64 | ! test -d /tmp
|
| 65 | test -d /tmp && test -d /tmp/foo
|
| 66 |
|
| 67 | The [simple_test_builtin](ref/chap-option.html#ysh:all) option enforces that
|
| 68 | `test` receives 3 or fewer arguments.
|
| 69 |
|
| 70 | ### Prefer Shell Functions to Aliases
|
| 71 |
|
| 72 | Functions subsume all the common uses of alias, and can be parsed statically.
|
| 73 |
|
| 74 | No:
|
| 75 |
|
| 76 | alias ll='ls -l'
|
| 77 |
|
| 78 | Yes:
|
| 79 |
|
| 80 | ll() { # Shell Style
|
| 81 | ls -l "$@"
|
| 82 | }
|
| 83 |
|
| 84 | proc ll { # YSH Style
|
| 85 | ls -l @ARGV
|
| 86 | }
|
| 87 |
|
| 88 | If you're wrapping an external command with a function of the same, use the
|
| 89 | [command](ref/chap-builtin-cmd.html#command) builtin:
|
| 90 |
|
| 91 | proc ls {
|
| 92 | command ls --color @ARGV
|
| 93 | }
|
| 94 |
|
| 95 | ### Prefer `$'\n'` to `echo -e`
|
| 96 |
|
| 97 | No:
|
| 98 |
|
| 99 | echo -e '\n' # arg to -e is dynamically parsed
|
| 100 |
|
| 101 | Yes:
|
| 102 |
|
| 103 | echo $'\n' # statically parsed
|
| 104 |
|
| 105 | ## How to Fix Code That `strict_errexit` Disallows
|
| 106 |
|
| 107 | The `strict_errexit` feature warns you when you would **lose errors** in shell
|
| 108 | code.
|
| 109 |
|
| 110 | ### The `local d=$(date %x)` Pitfall
|
| 111 |
|
| 112 | No:
|
| 113 |
|
| 114 | local d=$(date %x) # ignores failure
|
| 115 |
|
| 116 | Yes:
|
| 117 |
|
| 118 | local d
|
| 119 | d=$(date %x) # fails properly
|
| 120 |
|
| 121 | Better YSH style:
|
| 122 |
|
| 123 | var d = $(date %x) # fails properly
|
| 124 |
|
| 125 | ### Variations With `readonly` and `export`
|
| 126 |
|
| 127 | In these cases, the builtin comes after the assignment.
|
| 128 |
|
| 129 | No:
|
| 130 |
|
| 131 | readonly d1=$(date %x)
|
| 132 | export d2=$(date %x)
|
| 133 |
|
| 134 | Yes:
|
| 135 |
|
| 136 | d1=$(date %x)
|
| 137 | readonly d1
|
| 138 |
|
| 139 | d2=$(date %x)
|
| 140 | export d2
|
| 141 |
|
| 142 |
|
| 143 | ### The `if myfunc` Pitfall
|
| 144 |
|
| 145 | No:
|
| 146 |
|
| 147 | if myfunc; then
|
| 148 | echo 'Success'
|
| 149 | fi
|
| 150 |
|
| 151 | Shell workaround when the *$0 Dispatch Pattern* is used:
|
| 152 |
|
| 153 | myfunc() {
|
| 154 | echo hi
|
| 155 | }
|
| 156 |
|
| 157 | mycaller() {
|
| 158 | if $0 myfunc; then # $0 starts this script as a new process
|
| 159 | echo 'Success'
|
| 160 | fi
|
| 161 | }
|
| 162 |
|
| 163 | "$@" # invoked like myscript.sh mycaller arg1 arg2 ...
|
| 164 |
|
| 165 |
|
| 166 | Better YSH Style:
|
| 167 |
|
| 168 | try {
|
| 169 | myfunc
|
| 170 | }
|
| 171 | if (_error.code === 0)
|
| 172 | echo 'Success'
|
| 173 | }
|
| 174 |
|
| 175 |
|
| 176 | ## Remove Dynamic Parsing
|
| 177 |
|
| 178 | ### Replacing `declare -i`, `local -i`, ...
|
| 179 |
|
| 180 | The `-i` flag on assignment builtins doesn't add any functionality to bash —
|
| 181 | it's merely a different and confusing style.
|
| 182 |
|
| 183 | OSH doesn't support it; instead it has *true integers*.
|
| 184 |
|
| 185 | For example, don't rely on "punning" of the `+=` operator; use `$(( ))`
|
| 186 | instead.
|
| 187 |
|
| 188 | No:
|
| 189 |
|
| 190 | declare -i x=3
|
| 191 | x+=1 # Now it's '4' because += will do integer arithmetic
|
| 192 |
|
| 193 | Yes (shell style):
|
| 194 |
|
| 195 | x=3
|
| 196 | x=$(( x + 1 )) # No -i flag needed
|
| 197 |
|
| 198 | Yes (YSH style):
|
| 199 |
|
| 200 | var x = 3
|
| 201 | setvar x += 1
|
| 202 |
|
| 203 | Likewise, don't rely on dynamic parsing of arithmetic.
|
| 204 |
|
| 205 | No:
|
| 206 |
|
| 207 | declare -i x
|
| 208 | x='1 + 2' # Now it's the string '3'
|
| 209 |
|
| 210 | Yes (shell style):
|
| 211 |
|
| 212 | x=$(( 1 + 2 ))
|
| 213 |
|
| 214 | Yes (YSH style):
|
| 215 |
|
| 216 | var x = 1 + 2
|
| 217 |
|
| 218 |
|