Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bash background process modify global variable

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?

like image 884
algosolo Avatar asked Nov 03 '12 08:11

algosolo


2 Answers

Upgrade 2019

Playing with bash_ipc_demo adding completion and a graph generator.

Rendez-vous

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 and other shell

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.

Simple counter sample

$ 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 

More than one variables

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 

Get variable with snapshot

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 

Full useable demo:

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:

LastHourGraph

(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 
like image 56
F. Hauri Avatar answered Sep 19 '22 04:09

F. Hauri


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.

like image 45
doubleDown Avatar answered Sep 21 '22 04:09

doubleDown