3

I'm trying to have a script continuously run a loop that updates a variable in the background, so that the variable is up to date each time it is used. After looking for near an hour, I've come up with nothing, which is surprising, because it seems like someone would've wanted to do this before me.

My script, which is supposed to set $SESSIONLENGTH to the formatted string that is the length of the session...

#!/bin/bash

while true
do

SECONDSINSESSION=$SECONDS

SECONDSINMINUTE=60
SECONDSINHOUR=3600
SECONDSINDAY=86400

DAYSINSESSION=$(expr "$SECONDSINSESSION" / "$SECONDSINDAY")
DAY_REMAINDER=$(expr "$SECONDSINSESSION" % "$SECONDSINDAY")
HOURSINSESSION=$(expr "$DAY_REMAINDER" / "$SECONDSINHOUR")
HOUR_REMAINDER=$(expr "$DAY_REMAINDER" % "$SECONDSINHOUR")
MINUTESINSESSION=$(expr "$HOUR_REMAINDER" / "$SECONDSINMINUTE")
SECONDSINSESSION=$(expr "$HOUR_REMAINDER" % "$SECONDSINMINUTE")

SESSIONLENGTH="$DAYSINSESSION days, $HOURSINSESSION hours, $MINUTESINSESSION minutes, and $SECONDSINSESSION seconds."

sleep 1

done &

So when I verify that it's running with jobs (which it is) and use echo $SESSIONLENGTH, the value is either not updated, or not present at all.

Can anyone help me with this?

QUICK-EDIT: It should also be noted that this script needs to be ran through source or ..

  • That is not possible. The session of the background process is independent from its calling process as soon as it's launched. Not sure what you're trying to achieve but you might want to look at last which shows the session duration. – Julie Pelletier Jul 25 '16 at 4:34
  • The $SESSIONLENGTH you echo is nothing to do with the $SESSIONLENGTH in the script. They are variables in different processes, each with their own environment. – andy256 Jul 25 '16 at 4:34
  • @andy256 see the edit, that's not true. – Trevor Sears Jul 25 '16 at 4:37
  • 1
    @Trevor There is no echo $SESSIONLENGTH in the script, so the only place that occurs is when you type it in the shell (terminal session). Hence they are variables in different processes, each with their own environment. – andy256 Jul 25 '16 at 4:43
  • 1
    @Trevor It seems to me that you need a more basic and lengthy explanation than I can give you here. I suggest you read either the man page for the shell you are using, or an introductory text on shell programming. The "related" questions on the right side of this page also probably explain what's going on. – andy256 Jul 25 '16 at 4:54
1

Having resolved the OP's problem I thought I'd explain the issue and chosen resolution.

Having written the basic script, it was being tested by using the the source feature (or it's alias .). In this situation the script worked ok. It works because the shell variables are within the shell process.

When run as a background process (or just invoked as a command), the script runs as a separate process, forked from the shell process. The shell process knows nothing about the variables on the new process, explaining why

the value is either not updated, or not present at all.

There are many ways the two processes could communicate, but the one that we chose in chat was to use temporary files. At the command prompt or in another script, the OP can just cat myfile to see the most recent value of the variable.

This approach is suitable in this case because

  • it matches the OP's current level of expertize
  • the amount of data is low
  • the performance expectations are low.

We note the file system is handling concurrency issues, and in the circumstances the OP is not concerned about race conditions and other concurrency issues.

If only one copy of the script could run at a time, then the filename could be a constant. Otherwise a temporary file should be allocated; most shells have a means of allocating temporary files. If this were more of a production scenario, the script should put some effort into cleanup of old files.

| improve this answer | |
1

As you're using bash, you could trap the DEBUG signal, in order to recalculate SESSIONLENGTH before each command:

trap 'source /path/to/sessionlength' DEBUG

The sourced sessionlength script need only be the content of your loop, without the while and sleep parts around it:

SECONDSINSESSION=$SECONDS

SECONDSINMINUTE=60
SECONDSINHOUR=3600
SECONDSINDAY=86400

DAYSINSESSION=$(expr "$SECONDSINSESSION" / "$SECONDSINDAY")
DAY_REMAINDER=$(expr "$SECONDSINSESSION" % "$SECONDSINDAY")
HOURSINSESSION=$(expr "$DAY_REMAINDER" / "$SECONDSINHOUR")
HOUR_REMAINDER=$(expr "$DAY_REMAINDER" % "$SECONDSINHOUR")
MINUTESINSESSION=$(expr "$HOUR_REMAINDER" / "$SECONDSINMINUTE")
SECONDSINSESSION=$(expr "$HOUR_REMAINDER" % "$SECONDSINMINUTE")

SESSIONLENGTH="$DAYSINSESSION days, $HOURSINSESSION hours, $MINUTESINSESSION minutes, and $SECONDSINSESSION seconds."

bash also provides arithmetic expansion, so you can avoid spawning 6 expr processes each time by using

DAYSINSESSION=$((SECONDSINSESSION/SECONDSINDAY))

etc.

| improve this answer | |
1

There are a few technical things wrong with your script/approach, but forgetting the technical details for a moment, at an intuitive level what you are trying to do is set some counting process in motion, then at some point in the future - you want to interactively query the process to see how things are going by trying to reference the variable value directly.
You can't do this in bash because the bash shell is a synchronous language interpreter, which means while the bash process is running the while loop you can't interrupt it and say the programming equivalent of:

"hey bash, stop what you are doing, then go off and access some piece of memory (the $SESSIONLENGTH variable) and then print out its value to screen, then carry on whatever you were doing"

What you are trying to do is solved by concurrency and is the type of thing you do in asynchronous (nodejs, nginx) platforms or single threaded processes running an event loop like vim, or javascript in a web browser.

From the wikipedia concurrency article

A concurrent system is one where a computation can advance without waiting for all other computations to complete; where more than one computation can advance at the same time

I would add the qualifier or at least appear to the user as if more than one computation is advancing at the same time.
So what can you do in bash? you can either - as has been suggested, get bash to send its output somewhere else, and then read the data from a separate process or you could put this modified version of your code in a file:

session_state.sh

for i in seq 1 2 3 4 5
do

    SECONDSINSESSION=$SECONDS

    SECONDSINMINUTE=60
    SECONDSINHOUR=3600
    SECONDSINDAY=86400

    DAYSINSESSION=$(expr "$SECONDSINSESSION" / "$SECONDSINDAY")
    DAY_REMAINDER=$(expr "$SECONDSINSESSION" % "$SECONDSINDAY")
    HOURSINSESSION=$(expr "$DAY_REMAINDER" / "$SECONDSINHOUR")
    HOUR_REMAINDER=$(expr "$DAY_REMAINDER" % "$SECONDSINHOUR")
    MINUTESINSESSION=$(expr "$HOUR_REMAINDER" / "$SECONDSINMINUTE")
    SECONDSINSESSION=$(expr "$HOUR_REMAINDER" % "$SECONDSINMINUTE")

    SESSIONLENGTH="$DAYSINSESSION days, $HOURSINSESSION hours, $MINUTESINSESSION minutes, and $SECONDSINSESSION seconds."

    echo $SESSIONLENGTH
    sleep 2

done 

then source it:

$ source session_state.sh 
0 days, 0 hours, 46 minutes, and 26 seconds.
0 days, 0 hours, 46 minutes, and 28 seconds.
0 days, 0 hours, 46 minutes, and 30 seconds.
0 days, 0 hours, 46 minutes, and 32 seconds.
0 days, 0 hours, 46 minutes, and 34 seconds.
0 days, 0 hours, 46 minutes, and 36 seconds.

then query the variable

$ echo $SESSIONLENGTH
0 days, 0 hours, 46 minutes, and 36 seconds.

And you'll get the variable as it existed at its final state - but only after the process finished.
One theoretical solution to what you originally wanted, on a synchronous, single threaded shell, would be to

  • create a second process that monitored your keyboard input, that
  • had some pre-established arrangement with bash, so the second process could
  • queue your user generated requests so that when bash
  • finished its current frame/unit of code, it would
    • check the queue for user requests,
    • run them, then
  • continue where it last left off from

in other words an event loop.

| improve this answer | |
0

As I understand your requirements, you want to know for how long your script has been running.

If you create a file when your script is started, then you have the start time. Example with start time of crond since epoch:

$ stat -c %Y /var/run/crond.pid
1447709241

Second since crond started:

$ echo $(( $(date +%s)-$(stat -c %Y /var/run/crond.pid) ))
21740420

Shown as date:

$ date -d @$(( $(date +%s)-$(stat -c %Y /var/run/crond.pid) )) +%F\ %T
1970-09-09 16:09:56

If the program which should be reading the variable can not do the above calculation, then you should write $SESSIONLENGTH to a file, and then the other process should read that.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.