set -e is shadowing signal function - bash

I am running bash 4.4.19 on MacOS. I found the signal function doesn't work when if "set -e" is set. is it expected behavior? Here is my sample code:
#!/usr/local/bin/bash
set -e
declare -A Array1
Array1=([index1]="abc" [index2]="def" [index3]="dfkdjkfjdkdjfdk")
trap ReactSignal USR1
fun() {
PPid=$1
NUM=0
Array1[index4]="insidefunction"
while [ $NUM -le 5 ]
do
((NUM++))
echo "inside number is $NUM"
sleep 1
done
kill -USR1 $PPid
}
ReactSignal() {
IFS= read -r -d '' -u 3 checkOutput
echo "function output is ${checkOutput}"
}
Ppid="$$"
echo "start...."
coproc funfd { fun $Ppid; }
exec 3>&${funfd[0]}
echo "end...."
sleep 7
echo array value is ${Array1[#]}

It's because ((expression)) actually has a return code, as per the bash documentation:
((expression))
The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1.
So, of course, if NUM is zero going in to the expression, the return code will be one and set -e will pick this up as an error that needs termination.
There are any number of ways to solve this, from using the (rather ugly, in my opinion):
set +e ; ((NUM++)); set -e
to not using ((expression)) at all:
for i in {0..4} ; do
doSomethingWith $i
done

Related

How to share variable with sub-thread?

I would like to run a bash script with a watchdog function launched in sub thread that will stop my program when a given variable reach a value. This variable is incremented in the main thread.
var=0
function watchdog()
{
if [[ $var -ge 3 ]]; then
echo "Error"
fi
}
{ watchdog;} &
# main program loop
((var++))
The problem in this code is that $var stays at 0. I also tried without {} around the watchdog call, same result.
Is my code style good ?
You cannot share variables between processes in bash, and it does not support multi-threading. So you need a form of Inter-Process Communication. One of the simplest is to use a named pipe, also known as a FIFO.
Here is and example:
pipe='/tmp/mypipe'
mkfifo "$pipe"
var=0
# Your definition is not strictly correct (although it will work)
watchdog()
{
# Note the loop
while read var
do
if (( var >= 3 )) # a better way to do numeric comparisons
then
echo "Error $var"
else
echo "$var"
fi
sleep 2 # to prevent CPU hogging
done
}
watchdog < "$pipe" & # No need for a group
# main program loop - ??? I see no loop
((var++))
echo "$var" > "$pipe"
((var++))
echo "$var" > "$pipe"
((var++))
echo "$var" > "$pipe"
echo "waiting"
wait
rm "$pipe"
Example run:
$ bash gash.sh
1
waiting
2
Error 3
However I really don't see the point in using a separate process. Why not just call a function to test the value after each change?
if you run your bashscript with a . before, it will be use the same environment and can change existing variable. Look at this:
$ cat test.sh
#!/usr/bin/env bash
a=12
echo $a
$ a=1
$ echo $a
1
$ ./test.sh
12
$ echo $a
1
$ . ./test.sh
12
$ echo $a
12
After i run . ./test.sh the variable $a has been changed through the script.

Bash pass argument for array selection

I'm trying to pass the second argument to get an array and loop trough but im getting this error: ${$2[#]}: bad substitution
my code is:
/etc/init.d/displaycameras start c1
#!/bin/bash
dis1cam1="screen -dmS dis1cam1 sh -c 'omxplayer --avdict rtsp_transport:tcp --win \"0 0 640 428\" rtsp://myvideo --live -n -1'";
camera_feeds=('c1=(dis1cam1 dis1cam2 dis1cam3 dis1cam4 dis1cam5 dis1cam6 dis1cam8 dis1cam9)' 'c2=(dis2cam1 dis2cam2 dis2cam3 dis2cam4)')
for elt in "${camera_feeds[#]}";do eval $elt;done
# Start displaying camera feeds
case "$1" in
start)
for i in "${$2[#]}"
do
eval eval '$'$i
done
echo "Camera Display 1 Started"
;;
Is there a way to pass the 2nd argument to call the c2 set ?
in this way is working perfect:
#!/bin/bash
dis1cam1="screen -dmS dis1cam1 sh -c 'omxplayer --avdict rtsp_transport:tcp --win \"0 0 640 428\" rtsp://myvideo --live -n -1'";
camera_feeds=('c1=(dis1cam1 dis1cam2 dis1cam3 dis1cam4 dis1cam5 dis1cam6 dis1cam8 dis1cam9)' 'c2=(dis2cam1 dis2cam2 dis2cam3 dis2cam4)')
for elt in "${camera_feeds[#]}";do eval $elt;done
# Start displaying camera feeds
case "$1" in
start)
for i in "${c1[#]}"
do
eval eval '$'$i
done
echo "Camera Display 1 Started"
;;
I would strongly advise implementing this differently.
#!/usr/bin/env bash
die() { echo "$*" >&2; exit 1; }
[[ $BASH_VERSION = [0-3]* ]] && die "Bash 4.3 or newer needed"
[[ $BASH_VERSION = 4.[0-2].* ]] && die "Bash 4.3 or newer needed"
dis1cam1() { : "code to start camera dis1cam1 here"; )
dis1cam2() { : "code to start camera dis1cam2 here"; )
# ...etc...
camera_feeds__c1=(dis1cam1 dis1cam2 dis1cam3 dis1cam4 dis1cam5 dis1cam6 dis1cam8 dis1cam9)
camera_feeds__c2=(dis2cam1 dis2cam2 dis2cam3 dis2cam4)
# here, we're showing the iterate-over-all-feeds case
# you can just set var=camera_feeds__c1 yourself if you prefer
for var in "${!camera_feeds__#}"; do # var will be camera_feeds__c1 or camera_feeds__c2
feed_name=${var#camera_feeds__} # feed_name will be "c1" or "c2"
declare -n camera_feeds=$var
for i in "${camera_feeds[#]}"; do
echo "Starting $i in feed $feed_name" >&2
"$i" # look up and run code in variable named in $i
done
unset -n camera_feeds
done
"${camera_feeds__#}" expands to the list of shell variables whose names start with camera_feeds__; this is thus the name of our two arrays.
declare -n camera_feeds=$var then makes camera_feeds an alias for the array presently being iterated over, such that for i in "${camera_feeds[#]}" iterates over that array.
unset -n camera_feeds clears this association.

Work-around for $# unbound variable in Bash 4.0.0?

In specifically Bash version 4.0.0, is there any way to work around the use of an empty $# raising an unbound variable error when set -u is enabled?
Consider the following:
#!/usr/bin/env bash-4.0.0-1
set -xvu
echo "$BASH_VERSION"
echo "${BASH_VERSINFO[#]}"
main () {
printf '%q\n' "${#:-}"
}
main "${#:-}"
Gives me the following output when I provide an empty set of arguments:
neech#nicolaw.uk:~ $ ./test.sh
echo "$BASH_VERSION"
+ echo '4.0.0(1)-release'
4.0.0(1)-release
echo "${BASH_VERSINFO[#]}"
+ echo 4 0 0 1 release x86_64-unknown-linux-gnu
4 0 0 1 release x86_64-unknown-linux-gnu
main () {
printf '%q\n' "${#:-}"
}
main "${#:-}"
./test.sh: line 12: $#: unbound variable
I only see this behaviour in Bash version 4.0.0.
I was hoping that using variable substitution ${#:-} would allow me to work around this, but it seems not.
Is there a way to work around this?
$#, $* are special variables so should always be defined it's a bug
https://unix.stackexchange.com/questions/16560/bash-su-unbound-variable-with-set-u
a workaround, maybe:
set +u
args=("$#")
set -u
main "${args[#]}"
or maybe also
main "${#:+$#}"
Why not do error handling on your own? This way you can control exactly what happens when an exception is encountered, for instance return a custom exit code and message for that error, rather than be confined to some predefined behavior.
function log_error
{
[[ $# -ne 1 ]] && return 1
typeset msg="$1"
typeset timestamp=$(date "+%F %T")
echo "[${timestamp}] [ERROR] - $msg " >&2
}
if [[ -z "$BASH_VERSION" ]]
then
log_error "BASH VERSION is not set"
exit 1
fi

bash script to trap all interupts

so i have a simple lab exercise in class. Write and Interrupt/signal trapping program. this program will prompt user to guess the age of a grandmother. The user may guess as many times as possible. nothing will be able to terminate this program execpt when the user enters the right answer.
so my question is this. I have trapped ctrl_c but is there some "trick" or command i can use to trap ALL the interrupts or do i need to just make a statement for each signal i want to trap.
age=88
trap ctrl_c INT
function ctrl_c()
{
echo "**Trapped CTRL-C"
}
while [ 1 ]
do
echo "Please enter Grandmothers age. "
read ageGuess
echo $ageGuess
if [ $ageGuess == $age ]
then
echo "Exiting!"
exit
fi
done
Afaik trap xxx INT should be enough you can read more here and here, I will mention a few things about your script though:
The test command ([) uses = to compare two strings, not ==.
You should double-quote all your variables unless you are sure what will happen if they are not quoted, consider this:
a=a
b=a
# This will work since `$a` and `$b` contains a value
if [ $a = $b ]; then
echo hello
fi
This will fail since $c is empty and the statement will evaluate to: if [ = ]; then
if [ $c = $d ]; then
echo fail
fi
You should use true or : in your while loop:
while :; do
You should usually always use -r in read, and remember the shebang:
#!/bin/bash
age=88
trap ctrl_c INT
ctrl_c() {
echo "**Trapped CTRL-C"
}
while :; do
echo "Please enter Grandmothers age. "
read -r ageGuess
echo "$ageGuess"
if [ "$ageGuess" = "$age" ]; then
echo "Exiting!"
exit
fi
done
Actually the code could now use #!/bin/sh since it POSIX compatible.
Just note that you might want to use printf "%s\n" "$user_input" instead of echo "$user_input"

Bash get exit status of command when 'set -e' is active?

I generally have -e set in my Bash scripts, but occasionally I would like to run a command and get the return value.
Without doing the set +e; some-command; res=$?; set -e dance, how can I do that?
From the bash manual:
The shell does not exit if the command that fails is [...] part of any command executed in a && or || list [...].
So, just do:
#!/bin/bash
set -eu
foo() {
# exit code will be 0, 1, or 2
return $(( RANDOM % 3 ))
}
ret=0
foo || ret=$?
echo "foo() exited with: $ret"
Example runs:
$ ./foo.sh
foo() exited with: 1
$ ./foo.sh
foo() exited with: 0
$ ./foo.sh
foo() exited with: 2
This is the canonical way of doing it.
as an alternative
ans=0
some-command || ans=$?
Maybe try running the commands in question in a subshell, like this?
res=$(some-command > /dev/null; echo $?)
Given behavior of shell described at this question it's possible to use following construct:
#!/bin/sh
set -e
{ custom_command; rc=$?; } || :
echo $rc
Another option is to use simple if. It is a bit longer, but fully supported by bash, i.e. that the command can return non-zero value, but the script doesn't exit even with set -e. See it in this simple script:
#! /bin/bash -eu
f () {
return 2
}
if f;then
echo Command succeeded
else
echo Command failed, returned: $?
fi
echo Script still continues.
When we run it, we can see that script still continues after non-zero return code:
$ ./test.sh
Command failed, returned: 2
Script still continues.
Use a wrapper function to execute your commands:
function __e {
set +e
"$#"
__r=$?
set -e
}
__e yourcommand arg1 arg2
And use $__r instead of $?:
if [[ __r -eq 0 ]]; then
echo "success"
else
echo "failed"
fi
Another method to call commands in a pipe, only that you have to quote the pipe. This does a safe eval.
function __p {
set +e
local __A=() __I
for (( __I = 1; __I <= $#; ++__I )); do
if [[ "${!__I}" == '|' ]]; then
__A+=('|')
else
__A+=("\"\$$__I\"")
fi
done
eval "${__A[#]}"
__r=$?
set -e
}
Example:
__p echo abc '|' grep abc
And I actually prefer this syntax:
__p echo abc :: grep abc
Which I could do with
...
if [[ ${!__I} == '::' ]]; then
...

Resources