Guide to YSH Error Handling

There are just a few concepts to know:

Table of Contents
Examples
Handle command and expression errors with try
The failed builtin is a shortcut
Use a case statement if it's not just pass/fail
Error may have more attributes, like _error.message
The error builtin throws custom errors
Tips
Proc Return Status Should Be Either OK-Fail, or True-False-Fail
Related

Examples

Handle command and expression errors with try

Here's the most basic form:

try {
  ls /zz
}
if (_error.code !== 0) {
  echo "ls failed with $[_error.code]"
} 
# => ls failed with error 2

The failed builtin is a shortcut

Instead of writing if (_error.code !== 0), you can write if failed:

if failed {
  echo "ls failed with $[_error.code]"
} 

This saves you 7 punctuation characters: ( _ . !== )

Use a case statement if it's not just pass/fail

Sometimes it's nicer to use case rather than if:

try {
  grep '[0-9]+' foo.txt
}
case (_error.code) {
  (0)    { echo 'found' }
  (1)    { echo 'not found' }
  (else) { echo 'error invoking grep' }
}

Error may have more attributes, like _error.message

try {
  var x = fromJson('{')
}
if failed {
  echo "JSON failure: $[_error.message]"
}
# => JSON failure: expected string, got EOF

The error builtin throws custom errors

A non-zero exit code results in a simple shell-style error:

proc simple-failure {
  return 2
}

try {
  simple-failure
}
echo "status is $[_error.code]"
# => status is 2

The error builtin is more informative:

proc better-failure {
  error 'Custom message' (code=99, foo='zz')
}

try {
  better-failure
}
echo "$[_error.code] $[_error.message] foo=$[_error.foo]"
# => 99 Custom message foo=zz"

Tips

Proc Return Status Should Be Either OK-Fail, or True-False-Fail

That is, use one of these styles:

Return Status Meaning
0 OK
1 or more Fail
Return Status Meaning
0 True
1 False
2 or more Fail

For example, here's a proc that does is not follow the style:

proc is-different (left, right) {
  mkdir /tmp/dest            # may return 1 on failure

  cp $left $right /tmp/dest  # may return 1 on failure

  diff -u $left $right       # 0-true, 1-false, 2-failure
}

The exit code isn't well-defined, because mkdir and cp use the OK-fail paradigm, while diff uses the boolean paradigm:

Explicitly checking for failure fixes it:

proc different (left, right) {
  if ! mkdir /tmp/dest {
    return 2                 # 2-failure
  }
  if ! cp $left $right /tmp/dest {
    return 2                 # 2-failure
  }

  diff -u $left $right       # 0-true, 1-false, 2-failure
}

Related

Generated on Fri, 07 Nov 2025 16:29:37 +0000