Reputation: 343
In my Bash script, I have a global variable foo
set to some value and a function process back_func
that is run in the background. I would like the background process to be able to access foo
and modify its value, so that the change can be seen by the main process.
My script is structured in the following way:
#!/bin/bash
foo=0
function back_func {
foo=$(($foo+1))
echo "back $foo"
}
(back_func) &
echo "global $foo"
The output of the above script is
global 0
back 1
How can I get the global
and back
lines to both end with 1
? In other words, how can I make background process’s modification of foo
be picked up by the main process?
Upvotes: 34
Views: 19006
Reputation: 71007
Playing with bash_ipc_demo
adding completion and a graph generator.
If you wanna have two independant process which could communicate, you have to place a rendez-vous somewhere both process can reach.
This could be a simple file, a fifo pipe, a unix socket, a TCP socket or maybe else (Rexx port).
Bash don't have a equivalent to rexx port, so there is a little sample, using a rendez-vous file, that work (on my Linux).
I'm using shared memory /dev/shm
, to reduce disk load.
$ back_func() {
while :;do
echo $(($(</dev/shm/foo)+1)) >/dev/shm/foo;
sleep .3;
done;
}
Let play
$ echo 1 >/dev/shm/foo
$ back_func &
$ echo $(</dev/shm/foo)
4
$ echo $(</dev/shm/foo)
21
Than stop now:
$ fg
back_func
^C
or
$ kill $!
$
[1]+ Terminated back_func
For having many vars, there could by a nice manner:
$ back_func() {
declare -A MYGLOBAL
local vars
while :; do
((MYGLOBAL["counter"]++))
IFS=\ / read -a vars <<< "$(</proc/uptime) $(</proc/loadavg)"
MYGLOBAL["uptime"]=$vars
MYGLOBAL["idle"]=${vars[1]}
MYGLOBAL["l01m"]=${vars[2]}
MYGLOBAL["l05m"]=${vars[3]}
MYGLOBAL["l15m"]=${vars[4]}
MYGLOBAL["active"]=${vars[5]}
MYGLOBAL["procs"]=${vars[6]}
MYGLOBAL["lpid"]=${vars[7]}
MYGLOBAL["rand"]=$RANDOM
MYGLOBAL["crt"]=$SECONDS
declare -p MYGLOBAL > /dev/shm/foo
sleep 1
done
}
Then
$ back_func &
[1] 27429
$ . /dev/shm/foo
$ echo ${MYGLOBAL['counter']}
5
$ echo ${MYGLOBAL['lpid']}
27432
and from there, why not:
$ dumpMyGlobal() {
. /dev/shm/foo
printf "%8s " ${!MYGLOBAL[@]}
echo
printf "%8s " ${MYGLOBAL[@]}
echo
}
$ dumpMyGlobal
l15m uptime crt procs lpid active rand idle l05m
counter l01m
0.42 13815568.06 95 554 649 1 31135 21437004.95
0.38 73 0.50
$ dumpMyGlobal
l15m uptime crt procs lpid active rand idle l05m
counter l01m
0.41 13815593.29 120 553 727 2 3849 21437046.41
0.35 98 0.33
or
$ dumpMyGlobal() {
. /dev/shm/foo
sort <(
paste <(
printf "%-12s\n" ${!MYGLOBAL[@]}
) <(printf "%s\n" ${MYGLOBAL[@]})
)
}
$ dumpMyGlobal
active 1
counter 297
crt 337
idle 21435798.86
l01m 0.40
l05m 0.44
l15m 0.45
lpid 30418
procs 553
rand 7328
uptime 13814820.80
and finally getMyGlobalVar
function
$ declare -A MYGLOBALLOCK # snapshot variable
$ getMyGlobalVar () {
local i sync=false
[ "$1" == "--sync" ] && shift && sync=true
if [ -z "${MYGLOBALLOCK[*]}" ] || $sync; then
. /dev/shm/foo
for i in ${!MYGLOBAL[@]}
do
MYGLOBALLOCK[$i]=${MYGLOBAL[$i]}
done
fi
echo ${MYGLOBALLOCK[$1]}
}
will require --sync
flag for re-reading rendez-vous in order to let you look about each fields from the same snapshot.
$ getMyGlobalVar --sync idle
362084.12
$ getMyGlobalVar idle
362084.12
$ getMyGlobalVar rand
1533
$ getMyGlobalVar rand
1533
$ getMyGlobalVar --sync rand
43256
$ getMyGlobalVar idle
362127.63
There is a full sample: bash_ipc_demo or bash_ipc_demo.shz
You could use by:
wget http://f-hauri.ch/vrac/bash_ipc_demo
source bash_ipc_demo
back_func help
Usage: back_func [-q] [start [-g N]|stop|restart|status|get|dump|help]
-q Quiet
-g N Start daemon, setting uptime_useGraph to N values
back_func status
Background loop function is not running.
back_func start -g 3600
back_func status
Background loop function (19939) is running.
From there, if you source bash_ipc_demo
in another terminal, you could do the list into them.
You could even close the first terminal.
back_func dump
backFunc_count 13
backFunc_now 2016-04-06 17:03:19
backFunc_pid 19939
backFunc_running yes
backFunc_start 2016-04-06 17:03:07
cpu_numcores 2
loadavg_15min 0.44
loadavg_1min 0.66
loadavg_5min 0.54
loadavg_active 1
loadavg_last_pid 20005
loadavg_process 650
random 3714432
uptime_graph_val 3600
uptime_idle 425499.43
uptime_up 495423.53
uptime_usage1sec 9.90
uptime_usage 57.06
uptime_useGraph 57.06 8.91 7.50 6.93 12.00 9.41 7.84 9.90 7.50 11.88 7.92 9.31
9.90
Then, you could get one value
back_func get backFunc_pid newVar
echo $newVar
19939
or build a quick cpu graph:
lastMinuteGraph -p -o /tmp/lastMinuteGraph.png -W 640 -H 220
This will render a 640x220 PNG graphic, with uptime_graph_val
values.
In this case, as back_func start
was invoked with -g 3600
from more
than one hour, graphic show 3600 peek on 640 columns and 0-100% on 220 lines:
(Nota: Command was originaly named lastMinuteGraph
as 1st version of this just stored 60 values, now this use uptime_graph_val
for number of values to store. As I've used -g 3600
argument, this command could by named lastHourGraph
).
Then:
back_func stop
back_func get backFunc_end
2019-01-02 16:35:00
Upvotes: 37
Reputation: 2347
If the main process (let's call it main.sh) is another periodically running bash script then you could simply have the the other script (let's call it other.sh) write the value to a file (let's call this file value.sh).
other.sh
#! /bin/bash
echo "SOME_VAR=42" > /tmp/value.sh
main.sh
#! /bin/bash
. /tmp/value.sh
# Now you can use SOME_VAR
Upvotes: 4
Reputation: 8408
According to the Bash manual here,
If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell.
And since a process run in a subshell cannot modify the environment of the parent shell, I guess what you are trying to do is only possible via temp files / named pipes. Or you could rethink your approach.
Upvotes: 13