How to make bash execute a script every time it exits? - bash

I want to execute some commands every time I exit from bash, but cannot find a way to do it.
There is a ~/.bash_logout file for when you are logging out, but normally we use interactive shell instead of login shell, so this is not very useful for this purpose.
Is there a way to do this? Thanks!

You can trap the EXIT signal.
exit_handler () {
# code to run on exit
}
trap 'exit_handler' EXIT
Techinically, trap exit_handler EXIT would work as well. I quoted it to emphasize that the first argument to trap is a string that is essentially passed to eval, and not necessarily a single function name. You could just as easily write
trap 'do_this; do_that; if [[ $PICKY == yes ]]; then one_more_thing; fi' EXIT
rather than gather your code into a single function.

Related

How can I write and reference global bash scripts which halt the parent script's execution upon conditions?

I have written a simple script
get-consent-to-continue.sh
echo Would you like to continue [y/n]?
read response
if [ "${response}" != 'y' ];
then
exit 1
fi
I have added this script to ~/.bashrc as an alias
~/.bashrc
alias getConsentToContinue="source ~/.../get-consent-to-continue.sh"
My goal is to be able to call this from another script
~/.../do-stuff.sh
#!/usr/bin/env bash
# do stuff
getConsentToContinue
# do other stuff IF given consent, ELSE stop execution without closing terminal
Goal
I want to be able to
bash ~/.../do-stuff.sh
And then, when getConsentToContinue is called, if I respond with anything != 'y', then do-stuff.sh stops running without closing the terminal window.
The Problem
When I run
bash ~/.../do-stuff.sh
the alias is not accessible.
When I run
source ~/.../do-stuff.sh
Then the whole terminal closes when I respond with 'n'.
I just want to cleanly reuse this getConsentToContinue script to short-circuit execution of whatever script happens to be calling it. It's just for personal use when automating repetitive tasks.
A script can't force its parent script to exit, unless you source the script (since it's then executing in the same shell process).
Use an if statement to test how getConsentToContinue exited.
if ! getConsentToContinue
then
exit 1
fi
or more compactly
getConsentToContinue || exit
You could pass the PID of the calling script
For instance say you have a parent script called parent.sh:
# do stuff
echo "foo"
check_before_proceed $$
echo "bar"
Then, your check_before_proceed script would look like:
#!/bin/sh
echo Would you like to continue [y/n]?
read response
if [ "${response}" != 'y' ];then
kill -9 $1
fi
The $$ denotes the PID of the parent.sh script itself, you could find the relevant docs here. When we pass $$ as a parameter to the check_before_proceed script, then we would have access to the PID of the running parent.sh via the positional parameter$1 (see positional parameters)
Note: in my example, the check_before_proceed script would need to be accessible on $PATH

Run a command right before a script exits due to failure

Let's say there's this script
#!/bin/zsh
python -c 'a'
which will fail since a isn't defined. Just before the shell script exits, I want to run a command, say echo bye. How can that be achieved?
Flow is to be:
Python command above fails.
bye appears in terminal.
The zsh script exits.
I'd prefer it to affect the python command as little as possible such as indent, putting it in an if block, checking its exit code etc. In real life, the command is in fact multiple commands.
In the script you posted, the fact that the shell exits is unrelated to any error. The shell would exit, because the last argument hast been executed. Take for instance the script
#!/bin/zsh
python -c 'a'
echo This is the End
The final echo will always be exeuted, independent of the python command. To cause the script to exit, when python returns a non-zero exit code, you would write something like
#!/bin/zsh
python -c 'a' || exit $?
echo Successful
If you want to exit a script, whenever the first one of the commands produces a non-zeror exit status, AND at the same time want to print a message, you can use the TRAPZERR callback:
#!/bin/zsh
TRAPZERR() {
echo You have an unhandled non-zero exit code in your otherwise fabulous script
exit $?
}
python -c 'a'
echo Only Exit Code 0 encountered

How to safely exit early from a bash script?

I know there are several SO questions on exit vs. return in bash scripts (e.g. here).
On this topic, but different from existing questions, I believe, I'd like to know if there is a "best practice" for how to safely implement "early return" from a bash script such that the user's current shell is not exited if they source the script.
Answers such as this seem based on "exit", but if the script is sourced, i.e. run with a "." (dot space) prefix, the script runs in the current shell's context, in which case exit statements have the effect of exiting the current shell. I assume this is an undesirable result because a script doesn't know if it is being sourced or being run in a subshell - if the former, the user may unexpectedly have his shell disappear. Is there a way/best practice for early-returns to not exit the current shell if the caller sources it?
E.g. This script...
#! /usr/bin/bash
# f.sh
func()
{
return 42
}
func
retVal=$?
if [ "${retVal}" -ne 0 ]; then
exit "${retVal}"
# return ${retVal} # Can't do this; I get a "./f.sh: line 13: return: can only `return' from a function or sourced script"
fi
echo "don't wanna reach here"
...runs without killing my current shell if it is run from a subshell...
> ./f.sh
>
...but kills my current shell if it is sourced:
> . ./f.sh
One idea that comes to mind is to nest code within coditionals so that there is no explicit exit statement, but my C/C++ bias makes think of early-returns as aesthetically preferable to nested code. Are there other solutions that are truly "early return"?
The most common solution to bail out of a script without causing the parent shell to terminate is to try return first. If it fails then exit.
Your code will look like this:
#! /usr/bin/bash
# f.sh
func()
{
return 42
}
func
retVal=$?
if [ "${retVal}" -ne 0 ]; then
return ${retVal} 2>/dev/null # this will attempt to return
exit "${retVal}" # this will get executed if the above failed.
fi
echo "don't wanna reach here"
You can also use return ${retVal} 2>/dev/null || exit "${retVal}".
Hope this helps.

Writing a bash script, how do I stop my session from exiting when my script exits?

bash scripting noob here. I've found this article: https://www.shellhacks.com/print-usage-exit-if-arguments-not-provided/ that suggests putting
[ $# -eq 0 ] && { echo "Usage: $0 argument"; exit 1; }
at the top of a script to ensure arguments are passed. Seems sensible.
However, when I do that and test that that line does indeed work (by running the script without supplying any arguments: . myscript.sh) then the script does indeed exit but so does the bash session that I was calling the script from. This is very irritating.
Clearly I'm doing something wrong but I don't know what. Can anyone put me straight?
. myscript.sh is a synonym for source myscript.sh, which runs the script in the current shell (rather than as a separate process). So exit terminates your current shell. (return, on the other hand, wouldn't; it has special behaviour for sourced scripts.)
Use ./myscript.sh to run it "the normal way" instead. If that gives you a permission error, make it executable first, using chmod a+x myscript.sh. To inform the kernel that your script should be run with bash (rather than /bin/sh), add the following as the very first line in the script:
#!/usr/bin/env bash
You can also use bash myscript.sh if you can't make it executable, but this is slightly more error-prone (somebody might do sh myscript.sh instead).
Question seems not clear if you're sourcing script source script_name or . script_name it's interpreted in current bash process, if you're running a function it's the same it's running in same process, otherwise, calling a script, caller bash forks a new bash process and waits until it terminates (so running exit doesn't exit caller process), but when running exit builtin in in current bash it exits current process.

Difference between return and exit in Bash functions

What is the difference between the return and exit statement in Bash functions with respect to exit codes?
From man bash on return [n];
Causes a function to stop executing and return the value specified by n to its caller. If n is omitted, the return status is that of the last command executed in the function body.
... on exit [n]:
Cause the shell to exit with a status of n. If n is omitted, the exit status is that of the last command executed. A trap on EXIT is executed before the shell terminates.
EDIT:
As per your edit of the question, regarding exit codes, return has nothing to do with exit codes. Exit codes are intended for applications/scripts, not functions. So in this regard, the only keyword that sets the exit code of the script (the one that can be caught by the calling program using the $? shell variable) is exit.
EDIT 2:
My last statement referring exit is causing some comments. It was made to differentiate return and exit for the understanding of the OP, and in fact, at any given point of a program/shell script, exit is the only way of ending the script with an exit code to the calling process.
Every command executed in the shell produces a local "exit code": it sets the $? variable to that code, and can be used with if, && and other operators to conditionally execute other commands.
These exit codes (and the value of the $? variable) are reset by each command execution.
Incidentally, the exit code of the last command executed by the script is used as the exit code of the script itself as seen by the calling process.
Finally, functions, when called, act as shell commands with respect to exit codes. The exit code of the function (within the function) is set by using return. So when in a function return 0 is run, the function execution terminates, giving an exit code of 0.
return will cause the current function to go out of scope, while exit will cause the script to end at the point where it is called. Here is a sample program to help explain this:
#!/bin/bash
retfunc()
{
echo "this is retfunc()"
return 1
}
exitfunc()
{
echo "this is exitfunc()"
exit 1
}
retfunc
echo "We are still here"
exitfunc
echo "We will never see this"
Output
$ ./test.sh
this is retfunc()
We are still here
this is exitfunc()
I don't think anyone has really fully answered the question because they don't describe how the two are used. OK, I think we know that exit kills the script, wherever it is called and you can assign a status to it as well such as exit or exit 0 or exit 7 and so forth. This can be used to determine how the script was forced to stop if called by another script, etc. Enough on exit.
return, when called, will return the value specified to indicate the function's behavior, usually a 1 or a 0. For example:
#!/bin/bash
isdirectory() {
if [ -d "$1" ]
then
return 0
else
return 1
fi
echo "you will not see anything after the return like this text"
}
Check like this:
if isdirectory $1; then echo "is directory"; else echo "not a directory"; fi
Or like this:
isdirectory || echo "not a directory"
In this example, the test can be used to indicate if the directory was found. Notice that anything after the return will not be executed in the function. 0 is true, but false is 1 in the shell, different from other programming languages.
For more information on functions: Returning Values from Bash Functions
Note: The isdirectory function is for instructional purposes only. This should not be how you perform such an option in a real script.*
Remember, functions are internal to a script and normally return from whence they were called by using the return statement. Calling an external script is another matter entirely, and scripts usually terminate with an exit statement.
The difference "between the return and exit statement in Bash functions with respect to exit codes" is very small. Both return a status, not values per se. A status of zero indicates success, while any other status (1 to 255) indicates a failure. The return statement will return to the script from where it was called, while the exit statement will end the entire script from wherever it is encountered.
return 0 # Returns to where the function was called. $? contains 0 (success).
return 1 # Returns to where the function was called. $? contains 1 (failure).
exit 0 # Exits the script completely. $? contains 0 (success).
exit 1 # Exits the script completely. $? contains 1 (failure).
If your function simply ends without a return statement, the status of the last command executed is returned as the status code (and will be placed in $?).
Remember, return and exit give back a status code from 0 to 255, available in $?. You cannot stuff anything else into a status code (e.g., return "cat"); it will not work. But, a script can pass back 255 different reasons for failure by using status codes.
You can set variables contained in the calling script, or echo results in the function and use command substitution in the calling script; but the purpose of return and exit are to pass status codes, not values or computation results as one might expect in a programming language like C.
Sometimes, you run a script using . or source.
. a.sh
If you include an exit in the a.sh, it will not just terminate the script, but end your shell session.
If you include a return in the a.sh, it simply stops processing the script.
exit terminates the current process; with or without an exit code, consider this a system more than a program function. Note that when sourcing, exit will end the shell. However, when running, it will just exit the script.
return from a function go back to the instruction after the call, with or without a return code. return is optional and it's implicit at the end of the function. return can only be used inside a function.
I want to add that while being sourced, it's not easy to exit the script from within a function without killing the shell. I think, an example is better on a 'test' script:
#!/bin/bash
function die(){
echo ${1:=Something terrible wrong happen}
#... clean your trash
exit 1
}
[ -f /whatever/ ] || die "whatever is not available"
# Now we can proceed
echo "continue"
doing the following:
user$ ./test
Whatever is not available
user$
test -and- the shell will close.
user$ . ./test
Whatever is not available
Only test will finish and the prompt will show.
The solution is to enclose the potentially procedure in ( and ):
#!/bin/bash
function die(){
echo $(1:=Something terrible wrong happen)
#... Clean your trash
exit 1
}
( # Added
[ -f /whatever/ ] || die "whatever is not available"
# Now we can proceed
echo "continue"
) # Added
Now, in both cases only test will exit.
The OP's question:
What is the difference between the return and exit statement in BASH functions with respect to exit codes?
Firstly, some clarification is required:
A (return|exit) statement is not required to terminate execution of a (function|shell). A (function|shell) will terminate when it reaches the end of its code list, even with no (return|exit) statement.
A (return|exit) statement is not required to pass a value back from a terminated (function|shell). Every process has a built-in variable $? which always has a numeric value. It is a special variable that cannot be set like "?=1", but it is set only in special ways (see below *).
The value of $? after the last command to be executed in the (called function | sub shell) is the value that is passed back to the (function caller | parent shell). That is true whether the last command executed is ("return [n]"| "exit [n]") or plain ("return" or something else which happens to be the last command in the called function's code.
In the above bullet list, choose from "(x|y)" either always the first item or always the second item to get statements about functions and return, or shells and exit, respectively.
What is clear is that they both share common usage of the special variable $? to pass values upwards after they terminate.
* Now for the special ways that $? can be set:
When a called function terminates and returns to its caller then $? in the caller will be equal to the final value of $? in the terminated function.
When a parent shell implicitly or explicitly waits on a single sub shell and is released by termination of that sub shell, then $? in the parent shell will be equal to the final value of $? in the terminated sub shell.
Some built-in functions can modify $? depending upon their result. But some don't.
Built-in functions "return" and "exit", when followed by a numerical argument both set $? with their argument, and terminate execution.
It is worth noting that $? can be assigned a value by calling exit in a sub shell, like this:
# (exit 259)
# echo $?
3
In simple words (mainly for newbie in coding), we can say,
`return`: exits the function,
`exit()`: exits the program (called as process while running)
Also if you observed, this is very basic, but...,
`return`: is the keyword
`exit()`: is the function
If you convert a Bash script into a function, you typically replace exit N with return N. The code that calls the function will treat the return value the same as it would an exit code from a subprocess.
Using exit inside the function will force the entire script to end.
Adding an actionable aspect to a few of the other answers:
Both can give exit codes - default or defined by the function, and the only 'default' is zero for success for both exit and return. Any status can have a custom number 0-255, including for success.
Return is used often for interactive scripts that run in the current shell, called with . script.sh for example, and just returns you to your calling shell. The return code is then accessible to the calling shell - $? gives you the defined return status.
Exit in this case also closes your shell (including SSH connections, if that's how you're working).
Exit is necessary if the script is executable and called from another script or shell and runs in a subshell. The exit codes then are accessible to the calling shell - return would give an error in this case.
First of all, return is a keyword and exit is a function.
That said, here's a simplest of explanations.
return
It returns a value from a function.
exit
It exits out of or abandons the current shell.

Resources