OILS / doc / ysh-error.md View on Github | oils.pub

181 lines, 132 significant
1---
2default_highlighter: oils-sh
3---
4
5Guide to YSH Error Handling
6===========================
7
8There are just a few concepts to know:
9
10- [error][] builtin - "Throw" an error, with a custom message, error code, and
11 other properties.
12- [try][] builtin - Run a block, and set the [_error][] register to a `Dict`.
13 - `_error.code` will be `0` on success, or non-zero if an error is thrown in
14 the block.
15- [failed][] builtin - A handy shortcut to test for a non-zero error code.
16
17[try]: ref/chap-builtin-cmd.html#try
18[_error]: ref/chap-special-var.html#_error
19
20[error]: ref/chap-builtin-cmd.html#error
21[failed]: ref/chap-builtin-cmd.html#failed
22
23<div id="toc">
24</div>
25
26## Examples
27
28### Handle command and expression errors with `try`
29
30Here's the most basic form:
31
32 try {
33 ls /zz
34 }
35 if (_error.code !== 0) {
36 echo "ls failed with $[_error.code]"
37 }
38 # => ls failed with error 2
39
40
41### The `failed` builtin is a shortcut
42
43Instead of writing `if (_error.code !== 0)`, you can write `if failed`:
44
45 if failed {
46 echo "ls failed with $[_error.code]"
47 }
48
49This saves you 7 punctuation characters: `( _ . !== )`
50
51### Use a `case` statement if it's not just pass/fail
52
53Sometimes it's nicer to use `case` rather than `if`:
54
55 try {
56 grep '[0-9]+' foo.txt
57 }
58 case (_error.code) {
59 (0) { echo 'found' }
60 (1) { echo 'not found' }
61 (else) { echo 'error invoking grep' }
62 }
63
64### Error may have more attributes, like `_error.message`
65
66 try {
67 var x = fromJson('{')
68 }
69 if failed {
70 echo "JSON failure: $[_error.message]"
71 }
72 # => JSON failure: expected string, got EOF
73
74### The `error` builtin throws custom errors
75
76A non-zero exit code results in a simple shell-style error:
77
78 proc simple-failure {
79 return 2
80 }
81
82 try {
83 simple-failure
84 }
85 echo "status is $[_error.code]"
86 # => status is 2
87
88The `error` builtin is more informative:
89
90 proc better-failure {
91 error 'Custom message' (code=99, foo='zz')
92 }
93
94 try {
95 better-failure
96 }
97 echo "$[_error.code] $[_error.message] foo=$[_error.foo]"
98 # => 99 Custom message foo=zz"
99
100## Tips
101
102### Proc Return Status Should Be Either OK-Fail, or True-False-Fail
103
104<style>
105table {
106 margin-left: 2em;
107 background-color: #eee;
108}
109thead {
110 background-color: white;
111}
112</style>
113
114That is, use **one** of these styles:
115
116<div style="display: flex; gap: 20px;">
117<table cellpadding="10" cellspacing="5">
118
119- thead
120 - Return Status
121 - Meaning
122- tr
123 - 0
124 - OK
125- tr
126 - 1 or more
127 - Fail
128
129</table>
130
131<table cellpadding="10" cellspacing="5">
132
133- thead
134 - Return Status
135 - Meaning
136- tr
137 - 0
138 - True
139- tr
140 - 1
141 - False
142- tr
143 - 2 or more
144 - Fail
145
146</table>
147</div>
148
149For example, here's a proc that does is **not** follow the style:
150
151 proc is-different (left, right) {
152 mkdir /tmp/dest # may return 1 on failure
153
154 cp $left $right /tmp/dest # may return 1 on failure
155
156 diff -u $left $right # 0-true, 1-false, 2-failure
157 }
158
159The exit code isn't well-defined, because `mkdir` and `cp` use the OK-fail
160paradigm, while `diff` uses the **boolean** paradigm:
161
162Explicitly checking for failure fixes it:
163
164 proc different (left, right) {
165 if ! mkdir /tmp/dest {
166 return 2 # 2-failure
167 }
168 if ! cp $left $right /tmp/dest {
169 return 2 # 2-failure
170 }
171
172 diff -u $left $right # 0-true, 1-false, 2-failure
173 }
174
175## Related
176
177- [YSH vs. Shell Idioms > Error Handling](idioms.html#error-handling)
178- [YSH Fixes Shell's Error Handling (`errexit`)](error-handling.html) - A
179 detailed design doc
180
181