Problems with complex scripting in fish

Contents

Summary

I belive the design of fish as a scripting language (as of version 3.4) made developing redfish harder than it should have been. It has lead me to believe that you shouldn’t write complex scripts in fish. Make no mistake: I still very much like fish as an interactive shell.

My experience

Based on my experience, I do not recommend writing complex scripts in fish 3.4, except in service of its interactive use. Nontrivial shell scripts are already a questionable proposition, and I think fish is less suited for them than, for example, Bash.

Reasons

Error handling

My main reason is the error handling. The lack of set -e means scripts require adding or return after every command that can fail in order to propagate the error. This resembles error-handling in C or Go if the Go compiler didn’t complain about ignored errors. It is easy to miss a place where you are supposed to add an error check.

Simultaneously, fish lacks the equivalent of set -o pipefail. The way to handle pipeline errors is to check the list variable pipestatus yourself after the pipeline, which is also a check you can forget.

Not having pipefail causes a problem with loops in functions. When a loop’s output is piped to another command, and the loop body tries to return on error with or return, the pipeline will replace the returned status before returning from the function. The best solution I have found for this problem is to save the status in a variable, break out of the loop, then return the variable. There is an example in example.fish for redfish.

Substitution

Command substitution has also proved an issue, although smaller. When command output is substituted, it is split into multiple arguments on newlines. This happens unless substitution is performed inside double quotes. This is convenient in interactive use, but I quickly began to see it as a negative for scripting. When testing my scripts against somewhat tricky input like filenames with line feeds, I discovered that for a robust script you had to quote all command substitution that produced a single filename—similar to POSIX shell and Bash.

There is conflict here between ease of use and correctness or programming and interactive use. A version of fish that required you to split command output on line breaks explicitly would not require quoting to handle all filenames but would be more verbose and perhaps harder to start using interactively.

List variables also become multiple arguments. A list variable is substituted as multiple arguments by default and as a single argument when quoted. This was not a significant issue. It requires defensive quoting when you are unsure whether a variable can be a list, which seems rare in normal code. However, I would prefer for the code to indicate a substitution yields multiple arguments.

No linter

There is currently no ShellCheck for fish. One would be very helpful, because it could detect probable bugs like or return from inside a piped loop. I have not found a linter for fish code besides fish -n, which only validates syntax.

Conclusion

It pains me to say this, because I really like fish as an interactive shell, but I think right now it is a worse shell scripting language than GNU Bash. I would trust myself and other programmers to write less buggy shell scripts in Bash with ShellCheck than in fish. (Although I would recommend another language over both.) My current heuristic re: redfish is that when you want to use associative arrays in fish, you should switch to a language that has them.