algosolo
algosolo

Reputation: 343

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?

Upvotes: 34

Views: 19006

Answers (3)

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 71007

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).

and other

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

Upvotes: 37

ct_
ct_

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

doubleDown
doubleDown

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

Related Questions