In one of my build targets, I use sh.Output to check the output of a command. For example:
output, err := sh.Output("command", "--version")
But in my case, it's completely normal for "command" not to exist sometimes, so I want to handle that error differently from other failures that sh.Output might return. When the command doesn't exist, the underlying os/exec code will return an fs.PathError, and the idiomatic way to check for that is to use errors.Is:
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
// This is really an error.
return err
}
// File-doesn't-exist is an expected condition that we can handle.
}
But errors.Is returns false for errors returned by sh.Output.
What did you do?
As described above, I tried to use errors.Is to inspect errors returned by sh.Output and other command-executing functions from sh.
What did you expect to happen?
I expected errors.Is to recognize errors from sh functions as any of various common system errors, particularly fs.ErrNotExist.
What actually happened?
errors.Is fails to acknowledge that any sh error is like any other known error.
Environment
Mage Version: v1.15.0
OS: Linux
Additional context
This happens because errors in sh are constructed using fmt.Errorf("...%v", err). That inserts the value of err.String() but does not actually wrap the error, so any additional information about err is discarded. Changing it to fmt.Errorf("...%w", err) would solve it without affecting any other behavior. Error wrapping was introduced in Go 1.13.
My workaround for this issue is to inspect the text of the error message instead:
if err != nil {
if !strings.Contains(err.Error(), "no such file or directory") {
// This is really an error
return err
}
// File-doesn't-exist is an expected condition that we can handle.
}
Bug Description
In one of my build targets, I use
sh.Output
to check the output of a command. For example:But in my case, it's completely normal for "command" not to exist sometimes, so I want to handle that error differently from other failures that
sh.Output
might return. When the command doesn't exist, the underlyingos/exec
code will return anfs.PathError
, and the idiomatic way to check for that is to useerrors.Is
:But
errors.Is
returns false for errors returned bysh.Output
.What did you do?
As described above, I tried to use
errors.Is
to inspect errors returned bysh.Output
and other command-executing functions fromsh
.What did you expect to happen?
I expected
errors.Is
to recognize errors fromsh
functions as any of various common system errors, particularlyfs.ErrNotExist
.What actually happened?
errors.Is
fails to acknowledge that anysh
error is like any other known error.Environment
Additional context
This happens because errors in
sh
are constructed usingfmt.Errorf("...%v", err)
. That inserts the value oferr.String()
but does not actually wrap the error, so any additional information abouterr
is discarded. Changing it tofmt.Errorf("...%w", err)
would solve it without affecting any other behavior. Error wrapping was introduced in Go 1.13.My workaround for this issue is to inspect the text of the error message instead: