zsh - read user input with default value - empty input not empty? - macos

I wrote a function that asks for user input like this:
function is_confirmed {
read -rs -k 1 ans
if [[ "${ans}" == "n" || "${ans}" == "N" ]]; then
printf "No\n"
return 1
fi
if [[ "${ans}" == "y" || "${ans}" == "Y" ]]; then
printf "Yes\n"
return 0
fi
# here is my actual problem!!! this doesnt work when user input is blank!
if [[ "${ans}" == "" ]]; then
printf "Yes!\n"
return 1
fi
# Output is Damn!
printf "Damn"
return 1
}
works great so far, however, I want to set "yes" as the default answear, so when the user inputs nothing and just presses enter, it should fall back to "yes", so I tried it with || "$ans" == "" but that still falls back to "Damn"
how come? When I echo $ans at the end of the function it is empty...
EDIT 1:
This is what happens:
e_ask "Are you sure you want to install?\nWarning: This may override some files in your home directory."
if is_confirmed; then
echo "Great!"
else
e_error "Aborting..."
fi
here are the functions:
function e_ask {
printf "\n$1\n"
printf "(Y/n): "
}
function e_warn {
printf "Warning: $1\n"
}
function e_error {
printf "Error: $1\n"
exit 1
}

A case is better when you want to consider many different values and a default one
function is_confirmed {
read -rs -k 1 ans
case "${ans}" in
y|Y|$'\n')
printf "Yes\n"
return 0
;;
*) # This is the default
printf "No\n"
return 1
esac
}

Just add default variable in the beginning of function:
function is_confirmed {
ans="y"
echo "Put your choice:"
read $ans
}
If nothing is typed in, default value will remain.

The problem lies in how you wrote your conditions using the [[ builtin.
You need to change your conditions to:
function is_confirmed {
read -rs -k 1 ans
if [[ "${ans}" == "n" ]] || [[ "${ans}" == "N" ]]; then
printf "No\n"
return 1
else if [[ "${ans}" == "y" ]] || [[ "${ans}" == "Y" ]]; then
printf "Yes\n"
return 0
else if [[ -z "${ans}" ]]; then
printf "Empty\n"
else
printf "Damn\n"
fi
return 1
}
To explain, the [[ builtin tests just one condition, but you can chain multiple instances of [[ with && and ||. Your code, in contrast, tried to test 2 conditions inside [[ and used || as it's C/C++ usage.
More details available in help [[ or man bash

Related

Script enters all conditionals

I'd like to write a script that takes 2 parameters,
The first is parameter that could be "alpha/beta/tcp_friendliness/fast_convergence".
The Second should be a number for the alpha/beta cases and a 0/1 for the other 2.
for example: ./manage_cc alpha 512
Now i've wrote the following script that supposedly covers my cases, but it seems to go into all the conditionals. surely my syntax is broken so any help would be appreciated.
#!/bin/bash
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
if [["$1" == ""] || ["$2" == ""]]
then
echo "You need to pass a property to modify as a first parameter and a value as the second"
fi
if [["$1" == "alpha"] || ["$1" == "beta"]]
then
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
else
if [["$1" == "tcp_friendliness"] || ["$1" == "fast_convergence"]]
then
if [["$2" != "0"] && ["$2" != "1"]]
then
echo "This parameter only accepts a boolean value (0/1)"
exit 1
else
echo -n $2 > /sys/module/tcp_tuner/parameters/$1
fi
else
echo "The only accepted values for first parameter are alpha/beta/tcp_friendliness/fast_convergence"
exit 1
fi
fi
A rewrite of your code:
#!/usr/bin/env bash
write() {
printf "%s" "$2" > "/sys/module/tcp_tuner/parameters/$1"
}
die() {
echo "$*" >&2
exit 1
}
main() {
[[ -z $2 ]] && die "You need to pass a property to modify as a first parameter and a value as the second"
case $1 in
alpha|beta)
write "$1" "$2"
;;
tcp_friendliness|fast_convergence)
if [[ "$2" == "0" || "$2" == "1" ]]; then
write "$1" "$2"
else
die "This parameter only accepts a boolean value (0/1)"
fi
;;
*) die "The only accepted values for first parameter are alpha/beta/tcp_friendliness/fast_convergence"
;;
esac
}
main "$#"
Your condition syntax is wrong. When you start a condition with [[ you have to end it with ]], not just ]. They don't nest like parentheses.
if [[ "$1" == "alpha" || "$1" == "beta" ]]

command concatenation in bash

I want to invoke a function with some input checks (the input should be an integer between 1-21). If ok, then do the echo "invoke", else just print a message about invalid input.
I tried with the following simplified example, it works for the invalid case, but does not invoke for the valid case. what is wrong?
function _check_num ()
{
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ] || echo "input should be (1-21)" && return 1 // one-liner
}
function _call()
{
_check_num $1 && echo "invoke only if input is 1-21" // does not invoke given valid input
}
Note: please explain me the root cause of this one-liner case.
Do not use chains of && and || as a replacement for an if statement.
&& and || have equal precedence, so a && b || c runs c if either a or b fail; it is not equivalent to if a; then b; else c; fi.
a && b || c && d is parsed as ((a && b) ||c) && d, not (a && b) || (c && d).
Use an expicit if statement to make your code readable. (Also, don't use -a inside [...]; it is considered ambiguous and obsolete.)
function _check_num ()
{
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 21 ]; then
return 0
else
echo "input should be (1-21)" >&2
return 1
fi
}
The less readable version would be something like the following, uses braces to properly group the commands.
function _check_num ()
{
{
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 21 ]
} || {
echo "input should be (1-21)" >&2 && return 1
}
}
function _check_num ()
{
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ]; then
return 0
else
echo "input should be (1-21)"
return 1
fi
}
I thin you missed your return 0 statement
See chepner answer: problem is priority of && / || operators
Normally the return status of the first part should be 0 but idk something could go wrong.
You can use group last echo and return in {...} to make it work:
function _check_num () {
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ] ||
{ echo "input should be (1-21)" && return 1; }
}
Without {...} last return 1 is always returning 1 whether value is valid or invalid.
If you put your "one liner" inside a script to test it:
function _check_num () {
[[ "$1" =~ ^[0-9]+$ ]] &&
[ "$1" -ge 1 -a "$1" -le 21 ] ||
echo "input should be (1-21)" &&
return 1 # one-liner
}
_check_num "$1"; echo "exit value $?"
we get:
$ ./script.sh qwe
input should be (1-21)
exit value = 1
$ ./script.sh 112
input should be (1-21)
exit value = 1
$ ./script.sh 12
exit value = 1
As you can see, the exit value is always 1 (not a successful result).
Therefore you can not use the exit value as the trigger for other code.
Where?
The core issue is in this structure:
[ "$1" -ge 1 -a "$1" -le 21 ] || echo "input should be (1-21)" && return 1
which could be reduced to:
[ … ] || echo && return 1
The two possible exit values (success or not) of the […] could be tested with:
f(){ true || echo && return 1; }; f; echo "$?" # prints a 1.
f(){ false || echo && return 1; }; f; echo "$?" # *also* a 1[1].
[1] After also printing a blank line from the internal echo.
Why ?
Because of the "short circuit" effect of the shell «AND and OR lists»
From bash manual:
Lists
Of these list operators, && and || have equal precedence …
The return status is the exit status of the last command executed.
AND and OR lists are sequences of one of more pipelines separated by the && and || control operators, respectively.
What is crucial to understand the issue is this part (for AND):
An AND list has the form
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
And for OR
An OR list has the form
command1 || command2
command2 is executed if and only if command1 returns a non-zero exit status.
And the natural understanding of which should be the present exit status at any position:
The return status of AND and OR lists is the exit status of the last command executed in the list.
If you split any AND and OR list at any position, the exit status at such position is the exit status of the last previous command executed.
Some commands may get bypassed.
Step by step:
The first command is a test [[…]], the exit value at this point is its exit value.
This first command is connected to the next with an OR (||).
If the exit value of the first command is fail (not 0) the next command (echo) will be executed.
The exit code of echo is always success.
The next connection is an AND (&&).
At that point the exit value is true, the next command will be executed.
If the exit value of the first command is success (0) the next command will not be executed.
But the next one (return 1) will because the connection is an AND.
In both cases the return 1 is the last command executed.
The return value is always 1.
Precedence
Precedence may affect the order of the commands executed, but that does not explain why some commands are not executed.
Precedence in an "AND and OR list" is the same for && and ||.
So, operators will be considered in the left to right order they are found.
Associativity
Associativity in a shell "AND and OR list" is "left-associative".
That means that: if there are no parenthesis, operations are grouped from the left.
The first command is grouped with the second.
The result of that is grouped with the third, etc.
But even grouped as explained here. That does not explain why some commands are bypassed.
References
Advanced Bash-Scripting Guide: Chapter 26. List Constructs
Precedence
Operator associativity
Maybe you should consider using a function like:
function _check_num () {
declare -i num
if [[ $1 =~ ^[+]?(0+)?([0-9]+)$ ]]; then
num=${BASH_REMATCH[2]}
if ! (( num >=1 && num <= 21 )); then
echo "input should be (1-21)"
return 1
fi
return 0
else
echo "input should be a number"
return 2
fi
}

bash interactive menu, exit on empty input

I have tried to piece together a script to list different jws directories and allow user to select different client directories before it continues on to edit jnlp files within the directory. I have the edit part working, I have the menu working mostly; I can't figure out how to exit the loop once the selections have been made.
I'd like it to exit once ENTER is hit without a number selection, and continue with the next part of the script.
function update_jnlp
{
while :
do
# JNLP update submenu
options=($(ls /tmp/test/ | grep "jws$"))
menu() {
clear
echo "Locally installed jnlps:"
for i in ${!options[#]}; do
printf "%3d%s) %s\n" $((i+1)) "${choices[i]:- }" "${options[i]}"
done
[[ "$msg" ]] && echo "$msg"; :
}
prompt="Check an option (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
[[ "$num" != *[![:digit:]]* ]] &&
(( num > 0 && num <= ${#options[#]} )) ||
{ msg="Invalid option: $num"; continue; }
((num--)); msg="${options[num]} was ${choices[num]:+un}checked"
[[ "${choices[num]}" ]] && choices[num]="" || choices[num]="+"
done
printf "You selected"; msg=" nothing"
for i in ${!options[#]}; do
[[ "${choices[i]}" ]] && { printf " %s" "${options[i]}"; msg=""; }
done
done
for i in ${choices[#]}; do
printf "%s\n" ${choices[#]};
echo "fun"
done
echo "#msg"
# here is the script to edit the files now contained as ${choices[#]}
}
I recognize that I am in a menu loop and that I need to validate that input from the read command = "" or that $prompt input is null, and I assume I break from there
Please format your code more readably. As it stands, the erratic indentation makes it close to unreadable. Well, the indentation combined with defining a function in the body of a loop inside another function, and some of the more inscrutable loop bodies I've ever seen. (I thought I'd seen quite a lot; clearly, I've still got some learning to do.)
With the code formatted somewhat more orthodoxly, you have:
function update_jnlp
{
while :
do
# JNLP update submenu
options=($(ls /tmp/test/ | grep "jws$"))
menu() {
clear
echo "Locally installed jnlps:"
for i in ${!options[#]}; do
printf "%3d%s) %s\n" $((i+1)) "${choices[i]:- }" "${options[i]}"
done
[[ "$msg" ]] && echo "$msg"; :
}
prompt="Check an option (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
[[ "$num" != *[![:digit:]]* ]] &&
(( num > 0 && num <= ${#options[#]} )) ||
{ msg="Invalid option: $num"; continue; }
((num--)); msg="${options[num]} was ${choices[num]:+un}checked"
[[ "${choices[num]}" ]] && choices[num]="" || choices[num]="+"
done
printf "You selected"; msg=" nothing"
for i in ${!options[#]}; do
[[ "${choices[i]}" ]] && { printf " %s" "${options[i]}"; msg=""; }
done
echo "$msg" # Added
break # Added
done
for i in ${choices[#]}; do
printf "%s\n" ${choices[#]};
echo "fun"
done
echo "#msg"
# here is the script to edit the files now contained as ${choices[#]}
}
When I ran it, I selected 1, 3, 7 and then hit return, and the final page of output looked like:
Locally installed jnlps:
1+) abc.jws
2 ) def.jws
3+) ghi.jws
4 ) jkl.jws
5 ) mno.jws
6 ) pqr.jws
7+) stu.jws
8 ) vwx.jws
9 ) xyz.jws
stu.jws was checked
Check an option (again to uncheck, ENTER when done):
You selected abc.jws ghi.jws stu.jws
+
+
+
fun
+
+
+
fun
+
+
+
fun
#msg
Without the added break, you don't get to see what's selected because the outer while : loop clears the screen too quickly. With the echo "$msg" added, you get You selected nothing as you'd want.
The material after the main while : loop is clearly not finalized.
In fact, you don't need the while : loop at all, or the incomplete material. You could use:
function update_jnlp
{
# JNLP update submenu
options=($(ls /tmp/test/ | grep "jws$"))
menu() {
clear
echo "Locally installed jnlps:"
for i in ${!options[#]}; do
printf "%3d%s) %s\n" $((i+1)) "${choices[i]:- }" "${options[i]}"
done
[[ "$msg" ]] && echo "$msg"; :
}
prompt="Check an option (again to uncheck, ENTER when done): "
while menu && read -rp "$prompt" num && [[ -n "$num" ]]; do
[[ "$num" != *[![:digit:]]* ]] &&
(( num > 0 && num <= ${#options[#]} )) ||
{ msg="Invalid option: $num"; continue; }
((num--)); msg="${options[num]} was ${choices[num]:+un}checked"
[[ "${choices[num]}" ]] && choices[num]="" || choices[num]="+"
done
printf "You selected"; msg=" nothing"
for i in ${!options[#]}; do
[[ "${choices[i]}" ]] && { printf " %s" "${options[i]}"; msg=""; }
done
echo "$msg"
}
That's still rather inscrutable, but at least it works more or less sanely.

Is there a TRY CATCH command in Bash

I'm writing a shell script and need to check that a terminal app has been installed. I want to use a TRY/CATCH command to do this unless there is a neater way.
Is there a TRY CATCH command in Bash?
No.
Bash doesn't have as many luxuries as one can find in many programming languages.
There is no try/catch in bash; however, one can achieve similar behavior using && or ||.
Using ||:
if command1 fails then command2 runs as follows
command1 || command2
Similarly, using &&, command2 will run if command1 is successful
The closest approximation of try/catch is as follows
{ # try
command1 &&
#save your output
} || { # catch
# save log for exception
}
Also bash contains some error handling mechanisms, as well
set -e
it stops your script if any simple command fails.
And also why not if...else. It is your best friend.
Based on some answers I found here, I made myself a small helper file to source for my projects:
trycatch.sh
#!/bin/bash
function try()
{
[[ $- = *e* ]]; SAVED_OPT_E=$?
set +e
}
function throw()
{
exit $1
}
function catch()
{
export ex_code=$?
(( $SAVED_OPT_E )) && set +e
return $ex_code
}
function throwErrors()
{
set -e
}
function ignoreErrors()
{
set +e
}
here is an example how it looks in use:
#!/bin/bash
export AnException=100
export AnotherException=101
# start with a try
try
( # open a subshell !!!
echo "do something"
[ someErrorCondition ] && throw $AnException
echo "do something more"
executeCommandThatMightFail || throw $AnotherException
throwErrors # automaticatly end the try block, if command-result is non-null
echo "now on to something completely different"
executeCommandThatMightFail
echo "it's a wonder we came so far"
executeCommandThatFailsForSure || true # ignore a single failing command
ignoreErrors # ignore failures of commands until further notice
executeCommand1ThatFailsForSure
local result = $(executeCommand2ThatFailsForSure)
[ result != "expected error" ] && throw $AnException # ok, if it's not an expected error, we want to bail out!
executeCommand3ThatFailsForSure
# make sure to clear $ex_code, otherwise catch * will run
# echo "finished" does the trick for this example
echo "finished"
)
# directly after closing the subshell you need to connect a group to the catch using ||
catch || {
# now you can handle
case $ex_code in
$AnException)
echo "AnException was thrown"
;;
$AnotherException)
echo "AnotherException was thrown"
;;
*)
echo "An unexpected exception was thrown"
throw $ex_code # you can rethrow the "exception" causing the script to exit if not caught
;;
esac
}
I've developed an almost flawless try & catch implementation in bash, that allows you to write code like:
try
echo 'Hello'
false
echo 'This will not be displayed'
catch
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
You can even nest the try-catch blocks inside themselves!
try {
echo 'Hello'
try {
echo 'Nested Hello'
false
echo 'This will not execute'
} catch {
echo "Nested Caught (# $__EXCEPTION_LINE__)"
}
false
echo 'This will not execute too'
} catch {
echo "Error in $__EXCEPTION_SOURCE__ at line: $__EXCEPTION_LINE__!"
}
The code is a part of my bash boilerplate/framework. It further extends the idea of try & catch with things like error handling with backtrace and exceptions (plus some other nice features).
Here's the code that's responsible just for try & catch:
set -o pipefail
shopt -s expand_aliases
declare -ig __oo__insideTryCatch=0
# if try-catch is nested, then set +e before so the parent handler doesn't catch us
alias try="[[ \$__oo__insideTryCatch -gt 0 ]] && set +e;
__oo__insideTryCatch+=1; ( set -e;
trap \"Exception.Capture \${LINENO}; \" ERR;"
alias catch=" ); Exception.Extract \$? || "
Exception.Capture() {
local script="${BASH_SOURCE[1]#./}"
if [[ ! -f /tmp/stored_exception_source ]]; then
echo "$script" > /tmp/stored_exception_source
fi
if [[ ! -f /tmp/stored_exception_line ]]; then
echo "$1" > /tmp/stored_exception_line
fi
return 0
}
Exception.Extract() {
if [[ $__oo__insideTryCatch -gt 1 ]]
then
set -e
fi
__oo__insideTryCatch+=-1
__EXCEPTION_CATCH__=( $(Exception.GetLastException) )
local retVal=$1
if [[ $retVal -gt 0 ]]
then
# BACKWARDS COMPATIBILE WAY:
# export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[#]}-1)]}"
# export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[(${#__EXCEPTION_CATCH__[#]}-2)]}"
export __EXCEPTION_SOURCE__="${__EXCEPTION_CATCH__[-1]}"
export __EXCEPTION_LINE__="${__EXCEPTION_CATCH__[-2]}"
export __EXCEPTION__="${__EXCEPTION_CATCH__[#]:0:(${#__EXCEPTION_CATCH__[#]} - 2)}"
return 1 # so that we may continue with a "catch"
fi
}
Exception.GetLastException() {
if [[ -f /tmp/stored_exception ]] && [[ -f /tmp/stored_exception_line ]] && [[ -f /tmp/stored_exception_source ]]
then
cat /tmp/stored_exception
cat /tmp/stored_exception_line
cat /tmp/stored_exception_source
else
echo -e " \n${BASH_LINENO[1]}\n${BASH_SOURCE[2]#./}"
fi
rm -f /tmp/stored_exception /tmp/stored_exception_line /tmp/stored_exception_source
return 0
}
Feel free to use, fork and contribute - it's on GitHub.
bash does not abort the running execution in case something detects an error state (unless you set the -e flag). Programming languages which offer try/catch do this in order to inhibit a "bailing out" because of this special situation (hence typically called "exception").
In the bash, instead, only the command in question will exit with an exit code greater than 0, indicating that error state. You can check for that of course, but since there is no automatic bailing out of anything, a try/catch does not make sense. It is just lacking that context.
You can, however, simulate a bailing out by using sub shells which can terminate at a point you decide:
(
echo "Do one thing"
echo "Do another thing"
if some_condition
then
exit 3 # <-- this is our simulated bailing out
fi
echo "Do yet another thing"
echo "And do a last thing"
) # <-- here we arrive after the simulated bailing out, and $? will be 3 (exit code)
if [ $? = 3 ]
then
echo "Bail out detected"
fi
Instead of that some_condition with an if you also can just try a command, and in case it fails (has an exit code greater than 0), bail out:
(
echo "Do one thing"
echo "Do another thing"
some_command || exit 3
echo "Do yet another thing"
echo "And do a last thing"
)
...
Unfortunately, using this technique you are restricted to 255 different exit codes (1..255) and no decent exception objects can be used.
If you need more information to pass along with your simulated exception, you can use the stdout of the subshells, but that is a bit complicated and maybe another question ;-)
Using the above mentioned -e flag to the shell you can even strip that explicit exit statement:
(
set -e
echo "Do one thing"
echo "Do another thing"
some_command
echo "Do yet another thing"
echo "And do a last thing"
)
...
You can use trap:
try { block A } catch { block B } finally { block C }
translates to:
(
set -Ee
function _catch {
block B
exit 0 # optional; use if you don't want to propagate (rethrow) error to outer shell
}
function _finally {
block C
}
trap _catch ERR
trap _finally EXIT
block A
)
There are so many similar solutions which probably work. Below is a simple and working way to accomplish try/catch, with explanation in the comments.
#!/bin/bash
function a() {
# do some stuff here
}
function b() {
# do more stuff here
}
# this subshell is a scope of try
# try
(
# this flag will make to exit from current subshell on any error
# inside it (all functions run inside will also break on any error)
set -e
a
b
# do more stuff here
)
# and here we catch errors
# catch
errorCode=$?
if [ $errorCode -ne 0 ]; then
echo "We have an error"
# We exit the all script with the same error, if you don't want to
# exit it and continue, just delete this line.
exit $errorCode
fi
As everybody says, bash doesn't have a proper language-supported try/catch syntax. You can launch bash with the -e argument or use set -e inside the script to abort the entire bash process if any command has a non-zero exit code. (You can also set +e to temporarily allow failing commands.)
So, one technique to simulate a try/catch block is to launch a sub-process to do the work with -e enabled. Then in the main process, check the return code of the sub-process.
Bash supports heredoc strings, so you don't have to write two separate files to handle this. In the below example, the TRY heredoc will run in a separate bash instance, with -e enabled, so the sub-process will crash if any command returns a non-zero exit code. Then, back in the main process, we can check the return code to handle a catch block.
#!/bin/bash
set +e
bash -e <<TRY
echo hello
cd /does/not/exist
echo world
TRY
if [ $? -ne 0 ]; then
echo caught exception
fi
It's not a proper language-supported try/catch block, but it may scratch a similar itch for you.
You can do:
#!/bin/bash
if <command> ; then # TRY
<do-whatever-you-want>
else # CATCH
echo 'Exception'
<do-whatever-you-want>
fi
I use something like this:
YOUR COMMAND HERE
EXITCODE=$?
if [ "$EXITCODE" -ne "0" ]; then
#this is the catch part
echo "uh oh"
exit $EXITCODE
fi
this just checks the exit code of the command and compares it to zero
(which indicates the command ran successfully)
And you have traps http://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html which is not the same, but other technique you can use for this purpose
Assuming the terminal app in question is named 'app' (and you expect it to be in your $PATH):
if [[ ! `which app` ]]; then
# run code if app not installed
else
# run code if app is installed
fi
To piggyback Sean Mayo's answer, you can do like this
function check {
EXITCODE=$?
if [ "$EXITCODE" -ne "0" ]; then
#this is the catch part
echo "uh oh"
exit $EXITCODE
fi
}
YOUR COMMAND HERE
check
YOUR COMMAND HERE
check
YOUR COMMAND HERE
check
YOUR COMMAND HERE
check
It works well
I can recommend this in "bash -ue" mode:
set -ue
false && RET=$? || RET=$?
echo "expecting 1, got ${RET}"
true && RET=$? || RET=$?
echo "expecting 0, got ${RET}"
echo "test try...catch"
false && RET=$? || RET=$?
if [ ${RET} -ne 0 ]; then
echo "caught error ${RET}"
fi
echo "beware, using '||' before '&&' fails"
echo " -> memory aid: [A]nd before [O]r in the alphabet"
false || RET=$? && RET=$?
echo "expecting 1, got ${RET}"
true || RET=$? && RET=$?
echo "expecting 0, got ${RET}"
A very simple thing I use:
try() {
"$#" || (e=$?; echo "$#" > /dev/stderr; exit $e)
}
Below is an complete copy of the simplified script used in my other answer. Beyond additional error checking, there is an alias which allows the user to change the name of an existing alias. The syntax is given below. If the new_alias parameter is omitted, then the alias is removed.
ChangeAlias old_alias [new_alias]
The complete script is given below.
common.GetAlias() {
local "oldname=${1:-0}"
if [[ $oldname =~ ^[0-9]+$ && oldname+1 -lt ${#FUNCNAME[#]} ]]; then
oldname="${FUNCNAME[oldname + 1]}"
fi
name="common_${oldname#common.}"
echo "${!name:-$oldname}"
}
common.Alias() {
if [[ $# -ne 2 || -z $1 || -z $2 ]]; then
echo "$(common.GetAlias): The must be only two parameters of nonzero length" >&2
return 1;
fi
eval "alias $1='$2'"
local "f=${2##*common.}"
f="${f%%;*}"
local "v=common_$f"
f="common.$f"
if [[ -n ${!v:-} ]]; then
echo "$(common.GetAlias): $1: Function \`$f' already paired with name \`${!v}'" >&2
return 1;
fi
shopt -s expand_aliases
eval "$v=\"$1\""
}
common.ChangeAlias() {
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "usage: $(common.GetAlias) old_name [new_name]" >&2
return "1"
elif ! alias "$1" &>"/dev/null"; then
echo "$(common.GetAlias): $1: Name not found" >&2
return 1;
fi
local "s=$(alias "$1")"
s="${s#alias $1=\'}"
s="${s%\'}"
local "f=${s##*common.}"
f="${f%%;*}"
local "v=common_$f"
f="common.$f"
if [[ ${!v:-} != "$1" ]]; then
echo "$(common.GetAlias): $1: Name not paired with a function \`$f'" >&2
return 1;
elif [[ $# -gt 1 ]]; then
eval "alias $2='$s'"
eval "$v=\"$2\""
else
unset "$v"
fi
unalias "$1"
}
common.Alias exception 'common.Exception'
common.Alias throw 'common.Throw'
common.Alias try '{ if common.Try; then'
common.Alias yrt 'common.EchoExitStatus; fi; common.yrT; }'
common.Alias catch '{ while common.Catch'
common.Alias hctac 'common.hctaC -r; done; common.hctaC; }'
common.Alias finally '{ if common.Finally; then'
common.Alias yllanif 'fi; common.yllaniF; }'
common.Alias caught 'common.Caught'
common.Alias EchoExitStatus 'common.EchoExitStatus'
common.Alias EnableThrowOnError 'common.EnableThrowOnError'
common.Alias DisableThrowOnError 'common.DisableThrowOnError'
common.Alias GetStatus 'common.GetStatus'
common.Alias SetStatus 'common.SetStatus'
common.Alias GetMessage 'common.GetMessage'
common.Alias MessageCount 'common.MessageCount'
common.Alias CopyMessages 'common.CopyMessages'
common.Alias TryCatchFinally 'common.TryCatchFinally'
common.Alias DefaultErrHandler 'common.DefaultErrHandler'
common.Alias DefaultUnhandled 'common.DefaultUnhandled'
common.Alias CallStack 'common.CallStack'
common.Alias ChangeAlias 'common.ChangeAlias'
common.Alias TryCatchFinallyAlias 'common.Alias'
common.CallStack() {
local -i "i" "j" "k" "subshell=${2:-0}" "wi" "wl" "wn"
local "format= %*s %*s %-*s %s\n" "name"
eval local "lineno=('' ${BASH_LINENO[#]})"
for (( i=${1:-0},j=wi=wl=wn=0; i<${#FUNCNAME[#]}; ++i,++j )); do
name="$(common.GetAlias "$i")"
let "wi = ${#j} > wi ? wi = ${#j} : wi"
let "wl = ${#lineno[i]} > wl ? wl = ${#lineno[i]} : wl"
let "wn = ${#name} > wn ? wn = ${#name} : wn"
done
for (( i=${1:-0},j=0; i<${#FUNCNAME[#]}; ++i,++j )); do
! let "k = ${#FUNCNAME[#]} - i - 1"
name="$(common.GetAlias "$i")"
printf "$format" "$wi" "$j" "$wl" "${lineno[i]}" "$wn" "$name" "${BASH_SOURCE[i]}"
done
}
common.Echo() {
[[ $common_options != *d* ]] || echo "$#" >"$common_file"
}
common.DefaultErrHandler() {
echo "Orginal Status: $common_status"
echo "Exception Type: ERR"
}
common.Exception() {
common.TryCatchFinallyVerify || return
if [[ $# -eq 0 ]]; then
echo "$(common.GetAlias): At least one parameter is required" >&2
return "1"
elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
return "1"
fi
let "common_status = 10#$1"
shift
common_messages=()
for message in "$#"; do
common_messages+=("$message")
done
if [[ $common_options == *c* ]]; then
echo "Call Stack:" >"$common_fifo"
common.CallStack "2" >"$common_fifo"
fi
}
common.Throw() {
common.TryCatchFinallyVerify || return
local "message"
if ! common.TryCatchFinallyExists; then
echo "$(common.GetAlias): No Try-Catch-Finally exists" >&2
return "1"
elif [[ $# -eq 0 && common_status -eq 0 ]]; then
echo "$(common.GetAlias): No previous unhandled exception" >&2
return "1"
elif [[ $# -gt 0 && ( ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ) ]]; then
echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
return "1"
fi
common.Echo -n "In Throw ?=$common_status "
common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL #=$#"
if [[ $common_options == *k* ]]; then
common.CallStack "2" >"$common_file"
fi
if [[ $# -gt 0 ]]; then
let "common_status = 10#$1"
shift
for message in "$#"; do
echo "$message" >"$common_fifo"
done
if [[ $common_options == *c* ]]; then
echo "Call Stack:" >"$common_fifo"
common.CallStack "2" >"$common_fifo"
fi
elif [[ ${#common_messages[#]} -gt 0 ]]; then
for message in "${common_messages[#]}"; do
echo "$message" >"$common_fifo"
done
fi
chmod "0400" "$common_fifo"
common.Echo "Still in Throw $=$common_status subshell=$BASH_SUBSHELL #=$# -=$-"
exit "$common_status"
}
common.ErrHandler() {
common_status=$?
trap ERR
common.Echo -n "In ErrHandler ?=$common_status debug=$common_options "
common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL order=$common_order"
if [[ -w "$common_fifo" ]]; then
if [[ $common_options != *e* ]]; then
common.Echo "ErrHandler is ignoring"
common_status="0"
return "$common_status" # value is ignored
fi
if [[ $common_options == *k* ]]; then
common.CallStack "2" >"$common_file"
fi
common.Echo "Calling ${common_errHandler:-}"
eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
if [[ $common_options == *c* ]]; then
echo "Call Stack:" >"$common_fifo"
common.CallStack "2" >"$common_fifo"
fi
chmod "0400" "$common_fifo"
fi
common.Echo "Still in ErrHandler $=$common_status subshell=$BASH_SUBSHELL -=$-"
if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
return "$common_status" # value is ignored
else
exit "$common_status"
fi
}
common.Token() {
local "name"
case $1 in
b) name="before";;
t) name="$common_Try";;
y) name="$common_yrT";;
c) name="$common_Catch";;
h) name="$common_hctaC";;
f) name="$common_yllaniF";;
l) name="$common_Finally";;
*) name="unknown";;
esac
echo "$name"
}
common.TryCatchFinallyNext() {
common.ShellInit
local "previous=$common_order" "errmsg"
common_order="$2"
if [[ $previous != $1 ]]; then
errmsg="${BASH_SOURCE[2]}: line ${BASH_LINENO[1]}: syntax error_near unexpected token \`$(common.Token "$2")'"
echo "$errmsg" >&2
[[ /dev/fd/2 -ef $common_file ]] || echo "$errmsg" >"$common_file"
kill -s INT 0
return "1"
fi
}
common.ShellInit() {
if [[ common_initSubshell -ne BASH_SUBSHELL ]]; then
common_initSubshell="$BASH_SUBSHELL"
common_order="b"
fi
}
common.Try() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[byhl]" "t" || return
common_status="0"
common_subshell="$common_trySubshell"
common_trySubshell="$BASH_SUBSHELL"
common_messages=()
common.Echo "-------------> Setting try=$common_trySubshell at subshell=$BASH_SUBSHELL"
}
common.yrT() {
local "status=$?"
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[t]" "y" || return
common.Echo -n "Entered yrT ?=$status status=$common_status "
common.Echo "try=$common_trySubshell subshell=$BASH_SUBSHELL"
if [[ common_status -ne 0 ]]; then
common.Echo "Build message array. ?=$common_status, subshell=$BASH_SUBSHELL"
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
chmod "0600" "$common_fifo"
echo "$eof" >"$common_fifo"
common_messages=()
while read "message"; do
common.Echo "----> $message"
[[ $message != *$eof ]] || break
common_messages+=("$message")
done <"$common_fifo"
fi
common.Echo "In ytT status=$common_status"
common_trySubshell="$common_subshell"
}
common.Catch() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[yh]" "c" || return
[[ common_status -ne 0 ]] || return "1"
local "parameter" "pattern" "value"
local "toggle=true" "compare=p" "options=$-"
local -i "i=-1" "status=0"
set -f
for parameter in "$#"; do
if "$toggle"; then
toggle="false"
if [[ $parameter =~ ^-[notepr]$ ]]; then
compare="${parameter#-}"
continue
fi
fi
toggle="true"
while "true"; do
eval local "patterns=($parameter)"
if [[ ${#patterns[#]} -gt 0 ]]; then
for pattern in "${patterns[#]}"; do
[[ i -lt ${#common_messages[#]} ]] || break
if [[ i -lt 0 ]]; then
value="$common_status"
else
value="${common_messages[i]}"
fi
case $compare in
[ne]) [[ ! $value == "$pattern" ]] || break 2;;
[op]) [[ ! $value == $pattern ]] || break 2;;
[tr]) [[ ! $value =~ $pattern ]] || break 2;;
esac
done
fi
if [[ $compare == [not] ]]; then
let "++i,1"
continue 2
else
status="1"
break 2
fi
done
if [[ $compare == [not] ]]; then
status="1"
break
else
let "++i,1"
fi
done
[[ $options == *f* ]] || set +f
return "$status"
}
common.hctaC() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[c]" "h" || return
[[ $# -ne 1 || $1 != -r ]] || common_status="0"
}
common.Finally() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[ych]" "f" || return
}
common.yllaniF() {
common.TryCatchFinallyVerify || return
common.TryCatchFinallyNext "[f]" "l" || return
[[ common_status -eq 0 ]] || common.Throw
}
common.Caught() {
common.TryCatchFinallyVerify || return
[[ common_status -eq 0 ]] || return 1
}
common.EchoExitStatus() {
return "${1:-$?}"
}
common.EnableThrowOnError() {
common.TryCatchFinallyVerify || return
[[ $common_options == *e* ]] || common_options+="e"
}
common.DisableThrowOnError() {
common.TryCatchFinallyVerify || return
common_options="${common_options/e}"
}
common.GetStatus() {
common.TryCatchFinallyVerify || return
echo "$common_status"
}
common.SetStatus() {
common.TryCatchFinallyVerify || return
if [[ $# -ne 1 ]]; then
echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
return "1"
elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -lt 1 || 10#$1 -gt 255 ]]; then
echo "$(common.GetAlias): $1: First parameter was not an integer between 1 and 255" >&2
return "1"
fi
let "common_status = 10#$1"
}
common.GetMessage() {
common.TryCatchFinallyVerify || return
local "upper=${#common_messages[#]}"
if [[ upper -eq 0 ]]; then
echo "$(common.GetAlias): $1: There are no messages" >&2
return "1"
elif [[ $# -ne 1 ]]; then
echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
return "1"
elif [[ ${#1} -gt 16 || -n ${1%%[0-9]*} || 10#$1 -ge upper ]]; then
echo "$(common.GetAlias): $1: First parameter was an invalid index" >&2
return "1"
fi
echo "${common_messages[$1]}"
}
common.MessageCount() {
common.TryCatchFinallyVerify || return
echo "${#common_messages[#]}"
}
common.CopyMessages() {
common.TryCatchFinallyVerify || return
if [[ $# -ne 1 ]]; then
echo "$(common.GetAlias): $#: Wrong number of parameters" >&2
return "1"
elif [[ ${#common_messages} -gt 0 ]]; then
eval "$1=(\"\${common_messages[#]}\")"
else
eval "$1=()"
fi
}
common.TryCatchFinallyExists() {
[[ ${common_fifo:-u} != u ]]
}
common.TryCatchFinallyVerify() {
local "name"
if ! common.TryCatchFinallyExists; then
echo "$(common.GetAlias "1"): No Try-Catch-Finally exists" >&2
return "2"
fi
}
common.GetOptions() {
local "opt"
local "name=$(common.GetAlias "1")"
if common.TryCatchFinallyExists; then
echo "$name: A Try-Catch-Finally already exists" >&2
return "1"
fi
let "OPTIND = 1"
let "OPTERR = 0"
while getopts ":cdeh:ko:u:v:" opt "$#"; do
case $opt in
c) [[ $common_options == *c* ]] || common_options+="c";;
d) [[ $common_options == *d* ]] || common_options+="d";;
e) [[ $common_options == *e* ]] || common_options+="e";;
h) common_errHandler="$OPTARG";;
k) [[ $common_options == *k* ]] || common_options+="k";;
o) common_file="$OPTARG";;
u) common_unhandled="$OPTARG";;
v) common_command="$OPTARG";;
\?) #echo "Invalid option: -$OPTARG" >&2
echo "$name: Illegal option: $OPTARG" >&2
return "1";;
:) echo "$name: Option requires an argument: $OPTARG" >&2
return "1";;
*) echo "$name: An error occurred while parsing options." >&2
return "1";;
esac
done
shift "$((OPTIND - 1))"
if [[ $# -lt 1 ]]; then
echo "$name: The fifo_file parameter is missing" >&2
return "1"
fi
common_fifo="$1"
if [[ ! -p $common_fifo ]]; then
echo "$name: $1: The fifo_file is not an open FIFO" >&2
return "1"
fi
shift
if [[ $# -lt 1 ]]; then
echo "$name: The function parameter is missing" >&2
return "1"
fi
common_function="$1"
if ! chmod "0600" "$common_fifo"; then
echo "$name: $common_fifo: Can not change file mode to 0600" >&2
return "1"
fi
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_FILE_$RANDOM"
{ echo "$eof" >"$common_fifo"; } 2>"/dev/null"
if [[ $? -ne 0 ]]; then
echo "$name: $common_fifo: Can not write" >&2
return "1"
fi
{ while [[ $message != *$eof ]]; do
read "message"
done <"$common_fifo"; } 2>"/dev/null"
if [[ $? -ne 0 ]]; then
echo "$name: $common_fifo: Can not read" >&2
return "1"
fi
return "0"
}
common.DefaultUnhandled() {
local -i "i"
echo "-------------------------------------------------"
echo "$(common.GetAlias "common.TryCatchFinally"): Unhandeled exception occurred"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
}
common.TryCatchFinally() {
local "common_file=/dev/fd/2"
local "common_errHandler=common.DefaultErrHandler"
local "common_unhandled=common.DefaultUnhandled"
local "common_options="
local "common_fifo="
local "common_function="
local "common_flags=$-"
local "common_trySubshell=-1"
local "common_initSubshell=-1"
local "common_subshell"
local "common_status=0"
local "common_order=b"
local "common_command="
local "common_messages=()"
local "common_handler=$(trap -p ERR)"
[[ -n $common_handler ]] || common_handler="trap ERR"
common.GetOptions "$#" || return "$?"
shift "$((OPTIND + 1))"
[[ -z $common_command ]] || common_command+="=$"
common_command+='("$common_function" "$#")'
set -E
set +e
trap "common.ErrHandler" ERR
if true; then
common.Try
eval "$common_command"
common.EchoExitStatus
common.yrT
fi
while common.Catch; do
"$common_unhandled" >&2
break
common.hctaC -r
done
common.hctaC
[[ $common_flags == *E* ]] || set +E
[[ $common_flags != *e* ]] || set -e
[[ $common_flags != *f* || $- == *f* ]] || set -f
[[ $common_flags == *f* || $- != *f* ]] || set +f
eval "$common_handler"
return "$((common_status?2:0))"
}
Below is an example of a script which implements try/catch/finally in bash.
Like other answers to this question, exceptions must be caught after exiting a subprocess.
The example scripts start by creating an anonymous fifo, which is used to pass string messages from a command exception or throw to end of the closest try block. Here the messages are removed from the fifo and placed in an array variable. The status is returned through return and exit commands and placed in a different variable. To enter a catch block, this status must not be zero. Other requirements to enter a catch block are passed as parameters. If the end of a catch block is reached, then the status is set to zero. If the end of the finally block is reached and the status is still nonzero, then an implicit throw containing the messages and status is executed. The script requires the calling of the function trycatchfinally which contains an unhandled exception handler.
The syntax for the trycatchfinally command is given below.
trycatchfinally [-cde] [-h ERR_handler] [-k] [-o debug_file] [-u unhandled_handler] [-v variable] fifo function
The -c option adds the call stack to the exception messages.
The -d option enables debug output.
The -e option enables command exceptions.
The -h option allows the user to substitute their own command exception handler.
The -k option adds the call stack to the debug output.
The -o option replaces the default output file which is /dev/fd/2.
The -u option allows the user to substitute their own unhandled exception handler.
The -v option allows the user the option to pass back values though the use of Command Substitution.
The fifo is the fifo filename.
The function function is called by trycatchfinally as a subprocess.
Note: The cdko options were removed to simplify the script.
The syntax for the catch command is given below.
catch [[-enoprt] list ...] ...
The options are defined below. The value for the first list is the status. Subsquent values are the messages. If the there are more messages than
lists, then the remaining messages are ignored.
-e means [[ $value == "$string" ]] (the value has to match at least one string in the list)
-n means [[ $value != "$string" ]] (the value can not match any of the strings in the list)
-o means [[ $value != $pattern ]] (the value can not match any of the patterns in the list)
-p means [[ $value == $pattern ]] (the value has to match at least one pattern in the list)
-r means [[ $value =~ $regex ]] (the value has to match at least one extended regular expression in the list)
-t means [[ ! $value =~ $regex ]] (the value can not match any of the extended regular expressions in the list)
The try/catch/finally script is given below. To simplify the script for this answer, most of the error checking was removed. This reduced the size by 64%. A complete copy of this script can be found at my other answer.
shopt -s expand_aliases
alias try='{ common.Try'
alias yrt='EchoExitStatus; common.yrT; }'
alias catch='{ while common.Catch'
alias hctac='common.hctaC; done; }'
alias finally='{ common.Finally'
alias yllanif='common.yllaniF; }'
DefaultErrHandler() {
echo "Orginal Status: $common_status"
echo "Exception Type: ERR"
}
exception() {
let "common_status = 10#$1"
shift
common_messages=()
for message in "$#"; do
common_messages+=("$message")
done
}
throw() {
local "message"
if [[ $# -gt 0 ]]; then
let "common_status = 10#$1"
shift
for message in "$#"; do
echo "$message" >"$common_fifo"
done
elif [[ ${#common_messages[#]} -gt 0 ]]; then
for message in "${common_messages[#]}"; do
echo "$message" >"$common_fifo"
done
fi
chmod "0400" "$common_fifo"
exit "$common_status"
}
common.ErrHandler() {
common_status=$?
trap ERR
if [[ -w "$common_fifo" ]]; then
if [[ $common_options != *e* ]]; then
common_status="0"
return
fi
eval "${common_errHandler:-} \"${BASH_LINENO[0]}\" \"${BASH_SOURCE[1]}\" \"${FUNCNAME[1]}\" >$common_fifo <$common_fifo"
chmod "0400" "$common_fifo"
fi
if [[ common_trySubshell -eq BASH_SUBSHELL ]]; then
return
else
exit "$common_status"
fi
}
common.Try() {
common_status="0"
common_subshell="$common_trySubshell"
common_trySubshell="$BASH_SUBSHELL"
common_messages=()
}
common.yrT() {
local "status=$?"
if [[ common_status -ne 0 ]]; then
local "message=" "eof=TRY_CATCH_FINALLY_END_OF_MESSAGES_$RANDOM"
chmod "0600" "$common_fifo"
echo "$eof" >"$common_fifo"
common_messages=()
while read "message"; do
[[ $message != *$eof ]] || break
common_messages+=("$message")
done <"$common_fifo"
fi
common_trySubshell="$common_subshell"
}
common.Catch() {
[[ common_status -ne 0 ]] || return "1"
local "parameter" "pattern" "value"
local "toggle=true" "compare=p" "options=$-"
local -i "i=-1" "status=0"
set -f
for parameter in "$#"; do
if "$toggle"; then
toggle="false"
if [[ $parameter =~ ^-[notepr]$ ]]; then
compare="${parameter#-}"
continue
fi
fi
toggle="true"
while "true"; do
eval local "patterns=($parameter)"
if [[ ${#patterns[#]} -gt 0 ]]; then
for pattern in "${patterns[#]}"; do
[[ i -lt ${#common_messages[#]} ]] || break
if [[ i -lt 0 ]]; then
value="$common_status"
else
value="${common_messages[i]}"
fi
case $compare in
[ne]) [[ ! $value == "$pattern" ]] || break 2;;
[op]) [[ ! $value == $pattern ]] || break 2;;
[tr]) [[ ! $value =~ $pattern ]] || break 2;;
esac
done
fi
if [[ $compare == [not] ]]; then
let "++i,1"
continue 2
else
status="1"
break 2
fi
done
if [[ $compare == [not] ]]; then
status="1"
break
else
let "++i,1"
fi
done
[[ $options == *f* ]] || set +f
return "$status"
}
common.hctaC() {
common_status="0"
}
common.Finally() {
:
}
common.yllaniF() {
[[ common_status -eq 0 ]] || throw
}
caught() {
[[ common_status -eq 0 ]] || return 1
}
EchoExitStatus() {
return "${1:-$?}"
}
EnableThrowOnError() {
[[ $common_options == *e* ]] || common_options+="e"
}
DisableThrowOnError() {
common_options="${common_options/e}"
}
GetStatus() {
echo "$common_status"
}
SetStatus() {
let "common_status = 10#$1"
}
GetMessage() {
echo "${common_messages[$1]}"
}
MessageCount() {
echo "${#common_messages[#]}"
}
CopyMessages() {
if [[ ${#common_messages} -gt 0 ]]; then
eval "$1=(\"\${common_messages[#]}\")"
else
eval "$1=()"
fi
}
common.GetOptions() {
local "opt"
let "OPTIND = 1"
let "OPTERR = 0"
while getopts ":cdeh:ko:u:v:" opt "$#"; do
case $opt in
e) [[ $common_options == *e* ]] || common_options+="e";;
h) common_errHandler="$OPTARG";;
u) common_unhandled="$OPTARG";;
v) common_command="$OPTARG";;
esac
done
shift "$((OPTIND - 1))"
common_fifo="$1"
shift
common_function="$1"
chmod "0600" "$common_fifo"
}
DefaultUnhandled() {
local -i "i"
echo "-------------------------------------------------"
echo "TryCatchFinally: Unhandeled exception occurred"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
}
TryCatchFinally() {
local "common_errHandler=DefaultErrHandler"
local "common_unhandled=DefaultUnhandled"
local "common_options="
local "common_fifo="
local "common_function="
local "common_flags=$-"
local "common_trySubshell=-1"
local "common_subshell"
local "common_status=0"
local "common_command="
local "common_messages=()"
local "common_handler=$(trap -p ERR)"
[[ -n $common_handler ]] || common_handler="trap ERR"
common.GetOptions "$#"
shift "$((OPTIND + 1))"
[[ -z $common_command ]] || common_command+="=$"
common_command+='("$common_function" "$#")'
set -E
set +e
trap "common.ErrHandler" ERR
try
eval "$common_command"
yrt
catch; do
"$common_unhandled" >&2
hctac
[[ $common_flags == *E* ]] || set +E
[[ $common_flags != *e* ]] || set -e
[[ $common_flags != *f* || $- == *f* ]] || set -f
[[ $common_flags == *f* || $- != *f* ]] || set +f
eval "$common_handler"
}
Below is an example, which assumes the above script is stored in the file named simple. The makefifo file contains the script described in this answer. The assumption is made that the file named 4444kkkkk does not exist, therefore causing an exception to occur. The error message output from the ls 4444kkkkk command is automatically suppressed until inside the appropriate catch block.
#!/bin/bash
#
if [[ $0 != ${BASH_SOURCE[0]} ]]; then
bash "${BASH_SOURCE[0]}" "$#"
return
fi
source simple
source makefifo
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
ls 4444kkkkk
echo "leaving MyFunction3" >&4
}
MyFunction2() {
echo "entered MyFunction2" >&4
value="$(MyFunction3)"
echo "leaving MyFunction2" >&4
}
MyFunction1() {
echo "entered MyFunction1" >&4
local "flag=false"
try
(
echo "start of try" >&4
MyFunction2
echo "end of try" >&4
)
yrt
catch "[1-3]" "*" "Exception\ Type:\ ERR"; do
echo 'start of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
local -i "i"
echo "-------------------------------------------------"
echo "Status: $(GetStatus)"
echo "Messages:"
for ((i=0; i<$(MessageCount); i++)); do
echo "$(GetMessage "$i")"
done
echo "-------------------------------------------------"
break
echo 'end of catch "[1-3]" "*" "Exception\ Type:\ ERR"'
hctac >&4
catch "1 3 5" "*" -n "Exception\ Type:\ ERR"; do
echo 'start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
echo "-------------------------------------------------"
echo "Status: $(GetStatus)"
[[ $(MessageCount) -le 1 ]] || echo "$(GetMessage "1")"
echo "-------------------------------------------------"
break
echo 'end of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"'
hctac >&4
catch; do
echo 'start of catch' >&4
echo "failure"
flag="true"
echo 'end of catch' >&4
hctac
finally
echo "in finally"
yllanif >&4
"$flag" || echo "success"
echo "leaving MyFunction1" >&4
} 2>&6
ErrHandler() {
echo "EOF"
DefaultErrHandler "$#"
echo "Function: $3"
while read; do
[[ $REPLY != *EOF ]] || break
echo "$REPLY"
done
}
set -u
echo "starting" >&2
MakeFIFO "6"
TryCatchFinally -e -h ErrHandler -o /dev/fd/4 -v result /dev/fd/6 MyFunction1 4>&2
echo "result=$result"
exec >&6-
The above script was tested using GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17). The output, from running this script, is shown below.
starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "[1-3]" "*" "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 1
Messages:
Orginal Status: 1
Exception Type: ERR
Function: MyFunction3
ls: 4444kkkkk: No such file or directory
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure
Another example which uses a throw can be created by replacing function MyFunction3 with the script shown below.
MyFunction3() {
echo "entered MyFunction3" >&4
echo "This is from MyFunction3"
throw "3" "Orginal Status: 3" "Exception Type: throw"
echo "leaving MyFunction3" >&4
}
The syntax for the throw command is given below. If no parameters are present, then status and messages stored in the variables are used instead.
throw [status] [message ...]
The output, from executing the modified script, is shown below.
starting
entered MyFunction1
start of try
entered MyFunction2
entered MyFunction3
start of catch "1 3 5" "*" -n "Exception\ Type:\ ERR"
-------------------------------------------------
Status: 3
Exception Type: throw
-------------------------------------------------
start of catch
end of catch
in finally
leaving MyFunction1
result=failure

case or If string contains word1 and word2

In this example I want it to know if $# contains two words/symbols "load" and "/"
for one word/symbol this works
case "$#" in */*)
;;
echo "going to do stuff"
*)
echo "will do something else"
;;
esac
or
string='My string';
if [[ "$string" == *My* ]]
then
echo "It's there!";
fi
But if two words/symbols appear at random places I cant figure out how to do it.
Update:
The input will the module command. In this case I want to know if it is the module load with or without / that indicate version. the command will look like this
1) module load appname/1.1.1 or
2) module load appname
3) module (not load) (list, avail etc)
It is number 1 I am interested in for now.
3 will in some cases be variation of 1.
2 will be run as is but will include a message to the user
The slow way would be to iterate through the array twice and then check if both "load" and "/" were present, like this:
for element in $#; do [[ "$element" == "load" ]] && loadPresent=1; done
for element in $#; do [[ "$element" =~ ".*/.*" ]] && slashPresent=1; done
if [[ $loadPresent == 1 ]] && [[ $slashPresent == 1 ]]; then
echo "Contains load and /"
fi
(As I interpreted your question you want one parameter to be exactly "load" and another one to contain a slash.)
Something like this is possible:
if [[ ${#} =~ .*/.* && ${#} =~ ((^)|([ ]))load(($)|([ ])) ]]
then
echo both
fi
-or-
if LOAD=0 && SLASH=0 && \
for ARG in ${#};
do
if [ "${ARG#*/}" != "${ARG}" ]; then SLASH=1; fi
if [ "${ARG}" = "load" ]; then LOAD=1; fi
done && [ "${LOAD}${SLASH}" = "11" ];
then
echo both
fi
-or-
function loadslash()
{
LOAD=0 && SLASH=0
for ARG in ${#};
do
if [ "${ARG#*/}" != "${ARG}" ]; then SLASH=1; fi
if [ "${ARG}" = "load" ]; then LOAD=1; fi
done
test "${LOAD}${SLASH}" = "11"
}
if loadslash ${#}
then
echo both
fi
this will satisfy your requirements
if [[ $1 == "load" ]]; then
if [[ $2 == */* ]]; then
do first case
else
do second case
fi
else
do third case
fi

Resources