Does bash support exception? - bash

Is it possible to raise exceptions in bash? This can be useful, for example, when we want the script to exit when an error happens in a subcommand. Without exception, it seems the best we can do is to append || exit after each subcommand, which gives poor readability.
I didn't find descriptions about exceptions in bash manual. But I'm wondering whether there are ways to simulate them.

No, Bash does not have a notion of exceptions like other languages like Java have. The key unit of error-reporting in Bash is the exit code; functions, commands, and scripts all return 0 on success and non-zero to report some sort of error condition. Many programs document specific exit codes to report certain failure modes, for instance grep uses 1 to mean no-match-found and 2 to report other errors.
There are a number of useful debugging tricks you can take advantage of despite the lack of exceptions, including the caller command which enables some introspection of the current execution context.
Other resources:
How to debug a bash script?
Trace of executed programs called by bash script
Accessing function call stack in trap function

Related

are there any benefits of ending a bash script with exit

I encountered a bash script ending with the exit line. Would anything changes (save scaring users who 'source' rather than calling straight when the terminal closes )?
Note that I am not particularly interested in difference between exit and return. Here I am only interested in differences between having exit without parameters in the end of a bash script (one being closing console or process which sources the script rather than calling).
Could it be to address some less known shell dialects?
There are generally no benefits to doing this. There are only downsides, specifically the inability to source scripts like you say.
You can construct scenarios where it matters, such as having a sourceing script rely on it for termination on errors, or having a self-extracting archive header avoid executing its payload, but these unusual cases should not be the basis for a general guideline.
The one significant advantage is that it gives you explicit control over the return code.
Otherwise the return code of the script is going to be the return code of whatever the last command it executed happened to be. Which may or may not be indicative of the actual success or failure of the script as a whole.
A slightly less significant advantage is that if the last command's exit code is significant, and you follow it up with "exit $?" that tells the maintenance programmer coming along later that yes, you did consider what the exit code of the program should be and he shouldn't monkey with it without understanding why.
Conversely, of course, I wouldn't recommend ending a bash script with an explicit call to exit unless you really mean "ignore all previous exit codes and use this one". Because that's what anyone else looking at your code is going to assume you wanted and they're going to be annoyed that you wasted their time trying to figure out why if you did it just by rote and not for a reason.

Why do some people exit -1 rather than exit 1 on error?

I'm writing a rather trivial bash script; if I detect an error (not with a bad exit status from some other process) I want to exit with an exit status indicating an error (without being too specific).
It seems like I should be doing exit 1 (e.g. as per the TLDP Advanced Bash Scripting Guide, and the C Standard Library's stdlib.h header); yet I notice many people exit -1. Why is that?
TLDP's ABS is of questionable validity (in that it often uses, without comment, sub-par practices) so I wouldn't take it as a particular bastion of correctness about this.
That said valid command return codes are between 0 and 255 with 0 being "success". So yes, 1 is a perfectly valid (and common) error code.
Obviously I cannot say for certain why other people do that but I have two thoughts on the topic.
A failure to context switch (possibly combined with a lack of domain knowledge).
In many languages a return value of -1 from a function is a perfectly valid value and stands out from all the positive values that might (one assumes) normally be returned.
So attempting to extend that pattern (which the writer has picked up over time) to a shell script/etc. is a reasonable thing for them to do. Especially if they don't have the domain knowledge to realize that valid return codes are between 0 255.
An attempt to have those error exit lines "stand out" from normal exit cases (which may or may not be successful exits themselves) in an attempt to visually distinguish a certain set of extremely unlikely or otherwise extraordinary exit cases.
An exit of -1 does, actually, work it just doesn't get you a return code of -1 it gets you a return code of 255. (Try (exit -1); echo $? in your shell to see that.) So this isn't an entirely unreasonable thing to want to do (despite being confusing and complicit in perpetrating a confusion about exit codes).

why shell uses 0 as success?

In C and many other languages, 0 means false and 1 ( or non-zero )
means true. In the shell 0 for the status of a process means success
and non-zero means an error. Shell if statements essentially use 0
for true. Why did the writer of the first shell decide to use 0 for
true?
The OP asking about the
Why did the writer of the first shell decide to use 0 for true?
The short answer:
Isn't possible to answer this question in this form,
because the first shell has not any exit status.
EDIT
Because #John Kugelman asked for summarisation, this answer getting a bit long.
Nor the second, third and Nth shell hasn't anything that could be called "the exit status". For the approximative correct answer, need to go to deeper into the history.
The shell was invented
by "Louis Pouzin" as the "RUNCOM" tool, in
the MIT's Compatible Time-Sharing System
read about CTSS,
where is written:
Louis Pouzin also invented RUNCOM for CTSS. This facility, the
direct ancestor of the Unix shell script, allowed users to create
a file-system file of commands to be executed, with parameter
substitution. Louis also produced a design for the Multics shell,
ancestor of the Unix shell.
Read also this page.
The "next level" was in the Multics, called as
the Multics Command Language:
The command language interpreter, the Shell, is normally driven by
the Listener.
Its function is to listen for requests in the form of command lines
typed in at the user console. In the above command language
description, the listener reads in a line from the console, evaluates
the line as a command, and re-calls itself to repeat the function.
It hasn't anything that could be called "exit status".
The programs calls the terminate{file/process} procedure,
what stops the program execution.
And this is one of the most important points. Need differentiate between
the exit as system call
When you terminating a running (compiled) program, (for example, in "C" using the exit(N)) in the reality you call
a library function, what makes the bridge to the underlying operating system and correctly terminates the program.
This means the "exit(N)" (from the point of view of the exit function) is OS implementation-dependent.
See Wikipedia
shell's exit status -
The exit status of an executed command is the value returned by the waitpid system call or equivalent function.
Wikipedia. The waitpid like functions usually returns the exit status from the previous point, but it is OS implementation-dependent. (ofc, now standardized by POSIX).
Back to the history:
After the Multics, the UNIX got developed. From this page
the shell as we know today has two predecessors:
the Thompson shell (version 1 up to version 6) - if someone is interested, here is maintained a runnable version for the v6.
and the Programmers Work Bench [PWB] shell
the first shell that we known today was the Bourne shell V7
If interested, read through the manuals - links are here.
The first mentioning of the "exit code" is in the
Manual of the PWB (aka Mashey) Shell.
All shells "before" talks only about the:
Termination Reporting
If a command (not followed by "&") terminates abnormally,
a message is printed. (All terminations other than exit
and interrupt are considered abnormal.)
So, the answer to the question is in the above lines, - in line what is already said in the comments:
Because read the exit code as an error code. 0-mean no error. >0 some error.
Probably because there's only one mode of success and many modes of failure.
Nice citation from the PWB shell manual
$r is the exit status code of the preceding command.
``0'' is the normal return from most commands.
Notice the: most word. :), e.g. it was not invented in "one moment" by someone, but it is evolved in time. And it very tightly depends on the implementation of the exit and wait-like calls in the underlaying OS.
For example, the 1st version of UNIX know nothing about the exit(N) levels - it was a simple exit() and the wait didn't return a specific value - from the The first edition of the Unix Programmer's Manual, dated November 3, 1971
exit is the normal means of terminating a process. All files are
closed and the parent process is notified if it is executing a wait.
wait causes its caller to delay until one of its child processes terminates. If any child has already died, return is immediate; if
there are no children, return is immediate with the error bit set.
Also, read here - really worth - The Evolution of the Unix Time-sharing System - about the evolution of the fork, exec, exit and wait...
The V7 standardized the exit status as:
The value of a simple command is its
exit status if it terminates normally or 200+status if it
terminates abnormally (see signal(2) for a list of status
values).
also:
DIAGNOSTICS
Errors detected by the shell, such as syntax errors cause
the shell to return a non-zero exit status. If the shell is
being used non interactively then execution of the shell
file is abandoned. Otherwise, the shell returns the exit
status of the last command executed (see also exit).
Howg.. OMG... Someone could be so nice as to edit this post and correct my broken English and/or extend/correct the above...
And finally - sorry folks - totally of the topic - but can't resist adding the following image from the history - not many current users know Microsoft-XENIX. ;)
In most processors, the processor status indicates if the last instruction produced a zero value. One can branch if zero (or nonzero) without explicitly comparing with zero (saving instruction cycles). If you make zero success, that gives multiple ways to return failure (and save instructions).
This is actually a very poor convention. The VMS operating system from the 70's remains more advanced then nearly every over system in use today. It had a centralized error system in which applications could add their own error messages and codes which would be integrated with system error codes. In the VMS system, odd values were success and even values were failures.
Let's say you had a function that opens a file. You could have one success code that says it opened an existing file and another that says it created a new file.

How to exit the entire call stack of shell scripts if a child script fails?

I have set of shell scripts, around 20-30, that are used for performing one big task as a whole. The wrapper script calls mainly the high-level task scripts but internally those scripts calls other scripts like and the flow goes on in a nested manner.
I want to know if there is a way to exit the entire call stack if some critical script fails. Normally I run exit 125 command and then catch that in caller script and so on but I feel that little complicated. Is there a special exit that will abort the entire call stack? I don't want to use kill command to abort the wrapper script process.
You could have your main wrapper script start every sub-script in its own process group, using e.g. chpst -P.
Then the sub-scripts, as well as their children, could kill their own process group by sending it a KILL signal, and this would not affect the main wrapper script.
I think this would be a bad idea and what you're currently doing is the good way, though (because it makes the code easier to follow).

BASH shell process control - any other examples of controlling/scheduling work

I've inherited a medium sized project in which the main (batch) program is fed work through a large set of shell scripts that do a lot of process control (waiting for process to complete, sleeping, checking for conditions, etc) [ and reprocessed through perl scripts ]
Are there other examples of process control by shell scripts ? I would like to see what other people have done as a comparison. (as i'm not really fond of the 6,668 line shell script)
It may lead to that the current program works and doesn't need to be messed with or for maintenance reasons - it's too cumbersome and doing it another way will be easier to maintain, but I need other examples.
To reduce the "generality" of the question here's an example of what I'm looking for: procsup
Inquisitor project relies on process control from shell scripts extensively. You might want to see it's directory with main function set or directory with tests (i.e. slave processes) that it runs.
This is quite general question, and therefore giving specific answers may be a little bit difficult. (And you wont be happy with 5000 lines long example.) Most probably architecture of your application is faulty, and requires rather complete rework.
As you probably already know, process control with bash is pretty simple:
./test_script.sh &
test_script_pid=$!
wait $test_script_pid # waits until it's done
./test_script2.sh
echo $? # Prints return code of previous command
You can do same things with for example Python subprocess (or with Perl, obviously). If you have complex architecture with large number of different programs, then process is obviously non-trivial.
That is an awfully bug shell script. Have you considered refactoring it?
From the sound of it, there may be a lot of instances where you could replace several lines of code with a call to a shell function. If you can simplify the code in this way, then it will be easier to see where there are errors in the logic.
I've used this tactic successfully with a humongous PERL script and it turned out to have some serious logic errors and to be a security risk because it had embedded passwords that were obfuscated in an easily reversible way. The passwords that were exposed could have been used by persons unknown (well, a disgruntled employee) to shut down an entire global network.
Some managers were leaning towards making a security exception because this script was so important, but when the logic error was explained and it was clear that this script was providing incorrect data, it was decided that no data was better than dirty data. The guy who wrote that script taught himself programming with a PERL book and the writing of the script.

Resources