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.
Related
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
I'm trying to achieve a dynamic progress bar in bash script, the kind we see when installing new packages. In order to do this, a randomtask would call a progressbar script as a background task and feed it with some integer values.
The first script uses a pipe to feed the second.
#!/bin/bash
# randomtask
pbar_x=0 # percentage of progress
pbar_xmax=100
while [[ $pbar_x != $pbar_xmax ]]; do
echo "$pbar_x"
sleep 1
done | ./progressbar &
# do things
(( pbar_x++ ))
# when task is done
(( pbar_x = pbar_xmax ))
Hence, the second script needs to constantly receive the integer, and print it.
#!/bin/bash
# progressbar
while [ 1 ]; do
read x
echo "progress: $x%"
done
But here, the second script doesn't receive the values as they are updated. What did I do wrong ?
That can't work, the while loop is running in a subprocess, changes in the main program will not affect it in any way.
There are several IPC mechanisms, here I use a named pipe (FIFO):
pbar_x=0 # percentage of progress
pbar_xmax=100
pipename="mypipe"
# Create the pipe
mkfifo "$pipename"
# progressbar will block waiting on input
./progressbar < "$pipename" &
while (( pbar_x != pbar_xmax )); do
#do things
(( pbar_x++ ))
echo "$pbar_x"
sleep 1
# when task is done
#(( pbar_x = pbar_xmax ))
done > "$pipename"
rm "$pipename"
I also modified progressbar:
# This exits the loop when the pipe is closed
while read x
do
echo "progress: $x%"
done
With a third script you could use process substitution instead.
I'm on WSL, which means I can't use mkfifo. And coproc seemed to perfectly answer my need, so I searched and eventually found this:
coproc usage with exemples [bash-hackers wiki].
We start the process with coproc and redirect its output to stdout:
{ coproc PBAR { ./progressbar; } >&3; } 3>&1
Then we can access its in and out via file descriptors ${PBAR[0]}(output) and ${PBAR[1]}(input)
echo "$pbar_x" >&"${PBAR[1]}"
randomtask
#!/bin/bash
pbar_x=0 # percentage of progress
pbar_xmax=100
{ coproc PBAR { ./progressbar; } >&3; } 3>&1
while (( pbar_x <= 10)); do
echo $(( pbar_x++ )) >&"${PBAR[1]}"
sleep 1
done
# do things
echo $(( pbar_x++ )) >&"${PBAR[1]}"
# when task is done
echo $(( pbar_x = pbar_xmax )) >&"${PBAR[1]}"
progressbar
#!/bin/bash
while read x; do
echo "progress: $x%"
done
Please note that :
The coproc keyword is not specified by POSIX(R).
The coproc keyword appeared in Bash version 4.0-alpha
Please explain to me why the very last echo statement is blank? I expect that XCODE is incremented in the while loop to a value of 1:
#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output
if [ -z "$OUTPUT" ]
then
echo "Status WARN: No messages from SMcli"
exit $STATE_WARNING
else
echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
do
if [ "$STATUS" != "Optimal" ]
then
echo "CRIT: $NAME - $STATUS"
echo $((++XCODE))
else
echo "OK: $NAME - $STATUS"
fi
done
fi
echo $XCODE
I've tried using the following statement instead of the ++XCODE method
XCODE=`expr $XCODE + 1`
and it too won't print outside of the while statement. I think I'm missing something about variable scope here, but the ol' man page isn't showing it to me.
Because you're piping into the while loop, a sub-shell is created to run the while loop.
Now this child process has its own copy of the environment and can't pass any
variables back to its parent (as in any unix process).
Therefore you'll need to restructure so that you're not piping into the loop.
Alternatively you could run in a function, for example, and echo the value you
want returned from the sub-process.
http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL
The problem is that processes put together with a pipe are executed in subshells (and therefore have their own environment). Whatever happens within the while does not affect anything outside of the pipe.
Your specific example can be solved by rewriting the pipe to
while ... do ... done <<< "$OUTPUT"
or perhaps
while ... do ... done < <(echo "$OUTPUT")
This should work as well (because echo and while are in same subshell):
#!/bin/bash
cat /tmp/randomFile | (while read line
do
LINE="$LINE $line"
done && echo $LINE )
One more option:
#!/bin/bash
cat /some/file | while read line
do
var="abc"
echo $var | xsel -i -p # redirect stdin to the X primary selection
done
var=$(xsel -o -p) # redirect back to stdout
echo $var
EDIT:
Here, xsel is a requirement (install it).
Alternatively, you can use xclip:
xclip -i -selection clipboard
instead of
xsel -i -p
I got around this when I was making my own little du:
ls -l | sed '/total/d ; s/ */\t/g' | cut -f 5 |
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )
The point is that I make a subshell with ( ) containing my SUM variable and the while, but I pipe into the whole ( ) instead of into the while itself, which avoids the gotcha.
#!/bin/bash
OUTPUT="name1 ip ip status"
+export XCODE=0;
if [ -z "$OUTPUT" ]
----
echo "CRIT: $NAME - $STATUS"
- echo $((++XCODE))
+ export XCODE=$(( $XCODE + 1 ))
else
echo $XCODE
see if those changes help
Another option is to output the results into a file from the subshell and then read it in the parent shell. something like
#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
LINE="$LINE $line"
echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)
We have a shell script that is called by cron and runs as root.
This script outputs logging and debug info, and has been failing at one certain point. This point varies based on how much output the script creates (it fails sooner if we enable more debugging output, for example).
However, if the script is called directly, as a user, then it works without a problem.
We have since created a simplified test case which demonstrates the problem.
The script is:
#!/bin/bash
function log_so () {
local msg="$1"
if [ -z "${LOG_FILE}" ] ; then warn_so "It's pointless use log_so() if LOG_FILE variable is undefined!" ; return 1 ; fi
echo -e "${msg}"
echo -e "${msg}" >> ${LOG_FILE}
(
/bin/true
)
}
LOG_FILE="/usr/local/bin/log_bla"
linenum=1
while [[ $linenum -lt 2000 ]] ; do
log_so "short text: $linenum"
let linenum++
done
The highest this has reached is 244 before dying (when called via cron).
Some other searches recommended using a no-op subshell from the function and also calling /bin/true but not only did this not work, the subshell option is not feasible in the main script.
We have also tried changing the file descriptor limit for root, but that did not help, and have tried using both #!/bin/sh and #!/bin/bash for the script.
We are using bash 4.1.5(1)-release on Ubuntu 10.04 LTS.
Any ideas or recommendations for a workaround would be appreciated.
What about opening a fd by hand and cleaning it up afterwards? I don't have a bash 4.1 to test with, but it might help.
LOG_FILE="/usr/local/bin/log_bla"
exec 9<> "$LOG_FILE"
function log_so () {
local msg="$1"
if [ -z "${LOG_FILE}" ] ; then warn_so "It's pointless use log_so() if LOG_FILE variable is undefined!" ; return 1 ; fi
echo -e "${msg}"
echo -e "${msg}" >&9
return 0
}
linenum=1
while [[ $linenum -lt 2000 ]] ; do
log_so "short text: $linenum"
let linenum++
done
exec 9>&-
Please explain to me why the very last echo statement is blank? I expect that XCODE is incremented in the while loop to a value of 1:
#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output
if [ -z "$OUTPUT" ]
then
echo "Status WARN: No messages from SMcli"
exit $STATE_WARNING
else
echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
do
if [ "$STATUS" != "Optimal" ]
then
echo "CRIT: $NAME - $STATUS"
echo $((++XCODE))
else
echo "OK: $NAME - $STATUS"
fi
done
fi
echo $XCODE
I've tried using the following statement instead of the ++XCODE method
XCODE=`expr $XCODE + 1`
and it too won't print outside of the while statement. I think I'm missing something about variable scope here, but the ol' man page isn't showing it to me.
Because you're piping into the while loop, a sub-shell is created to run the while loop.
Now this child process has its own copy of the environment and can't pass any
variables back to its parent (as in any unix process).
Therefore you'll need to restructure so that you're not piping into the loop.
Alternatively you could run in a function, for example, and echo the value you
want returned from the sub-process.
http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL
The problem is that processes put together with a pipe are executed in subshells (and therefore have their own environment). Whatever happens within the while does not affect anything outside of the pipe.
Your specific example can be solved by rewriting the pipe to
while ... do ... done <<< "$OUTPUT"
or perhaps
while ... do ... done < <(echo "$OUTPUT")
This should work as well (because echo and while are in same subshell):
#!/bin/bash
cat /tmp/randomFile | (while read line
do
LINE="$LINE $line"
done && echo $LINE )
One more option:
#!/bin/bash
cat /some/file | while read line
do
var="abc"
echo $var | xsel -i -p # redirect stdin to the X primary selection
done
var=$(xsel -o -p) # redirect back to stdout
echo $var
EDIT:
Here, xsel is a requirement (install it).
Alternatively, you can use xclip:
xclip -i -selection clipboard
instead of
xsel -i -p
I got around this when I was making my own little du:
ls -l | sed '/total/d ; s/ */\t/g' | cut -f 5 |
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )
The point is that I make a subshell with ( ) containing my SUM variable and the while, but I pipe into the whole ( ) instead of into the while itself, which avoids the gotcha.
#!/bin/bash
OUTPUT="name1 ip ip status"
+export XCODE=0;
if [ -z "$OUTPUT" ]
----
echo "CRIT: $NAME - $STATUS"
- echo $((++XCODE))
+ export XCODE=$(( $XCODE + 1 ))
else
echo $XCODE
see if those changes help
Another option is to output the results into a file from the subshell and then read it in the parent shell. something like
#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
LINE="$LINE $line"
echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)