This is the third post in the “bashing bash” series. The aim of the series is to highlight problems in bash and convince systems and software engineers to help building a better alternative.
The problem
What is the output of the following script?
#!/bin/bash set -eu echo Here false echo Not here
Right, the output is
Here
Imagine that instead of false
you have a lot of code. Since there are no exceptions, you have no idea where the error occurred.
Solutions using bash:
- Use
set -x
to trace the code. - Add
echo something
every here and there to know between which twoecho
‘s the error occurred. - Catch the error using
trap
and print the line number as suggested on StackOverflow . Writing this additional catching snippet at the top of every script is not really convenient.
This problem of unclear error location is unimaginable in any normal programming language.
“There is no problem, just don’t do it”
Bash was not intended to be a “normal” programming language. Some people say it’s an abuse of bash to use it as such. Looking at the code written in Bash I can tell it really is an abuse in many cases.
The reality though is that bash is still (ab)used for programming. In some cases Bash has positive aspects which outweigh the need to use other languages. In other cases a program starts as a small Bash script and is just not rewritten in another language after the script grows.
I suggest making a better shell rather than convincing people not to abuse Bash. People will keep on doing what they are doing. Let’s make their lives easier by providing them with a better shell.
The suggested solution
Use NGS. In NGS, any failed process throws an exception. Let’s take a look at the script below
#!/usr/bin/env ngs echo Here false echo Not here
What’s the output?
Here Not here
WAT?
Well, actually false
returning an exit code of 1 is not an exception, it’s normal. If any command returning non-zero code would cause an exception, you wouldn’t be able to write for example if $(test -e myfile) do_something
.
Failed process is a process that returns an unexpected exit code. Here is the part of stdlib
that defines what’s a fail and what’s not:
F finished_ok(p:Process) p.exit_code == 0
F finished_ok(p:Process) {
guard p.executable.path == '/bin/false'
p.exit_code == 1
}
F finished_ok(p:Process) {
guard p.executable.path == '/usr/bin/test'
p.exit_code in [0, 1]
}
Such definitions also mean that you can easily extend NGS to work properly with any other command, simply by adding another finished_ok
function. (Or add it to stdlib
if it’s a common command so everyone would benefit).
So where are the exceptions?
We’ll have to modify the code to get an unexpected exit code. Example:
#!/usr/bin/env ngs
echo Here
ls nosuchfile
echo Not here
Output:
Here ls: cannot access 'nosuchfile': No such file or directory ========= Uncaught exception of type 'ProcessFailed' ========= ====== Exception of type 'ProcessFailed' ====== === [ backtrace ] === [Frame #0] /etc/ngs/bootstrap.ngs:158:1 - 158:10 [in <anonymous>] [Frame #1] /etc/ngs/bootstrap.ngs:154:17 - 154:29 [in bootstrap] [Frame #2] ./2.ngs:3:4 - 3:14 [in <anonymous>] [Frame #3] /usr/share/ngs/stdlib.ngs:1116:11 - 1116:15 [in $()] [Frame #4] /usr/share/ngs/stdlib.ngs:1050:29 - 1050:42 [in wait] [Frame #5] /usr/share/ngs/stdlib.ngs:1006:7 - 1006:20 [in ProcessFailed] === [ dump process ] === (a lot of not very well formatted output with info about the process)
Please help building a better alternative
Go to https://github.com/ilyash/ngs/ and contribute some code.