0

I have been working on a script for my father that manages several tasks through a script with many functions that chain together (organization purposes). This script relies entirely on zenity for all user interaction (my father is really tech illiterate {Age 60+}).

The issue I'm having is that once I call the function transprog it won't exit the loop even after the variable that the while loop relies on is changed outside of the function.

The loop is called here:

copystat="1"
transprog & cp -rf "$netlocdirFIN/" "$transdir2/" # "transprog" is called here
copystat="0"
sleep 1
ret="$?"
echo "$ret"
transnew #calls next step/function

And this is the transprog function:

transprog ()
{
i2="Im Working On It!"

(
while [ $copystat = 1 ]; do
# =================================================================
echo "25"
echo "# ^_^ $i2" ; sleep 1

# =================================================================
echo "50"
echo "# ^-^ $i2" ; sleep 1

# =================================================================
echo "75"
echo "# ^_^ $i2" ; sleep 1


# =================================================================
echo "99"
echo "# ^-^ $i2" ; sleep 1

done
echo "# Your files are DONE TRANSFERING!" ; sleep 1
echo "100"
) |
zenity --progress \
  --title="File Transfer In Progress" \
  --text="# Im Getting Warmed Up" \
  --percentage=0 \
  --auto-close \
  --auto-kill
}

Once the copy is done the script continues on: the zenity progress bar is in the background ticking away and it really confuses him.

How can I tell the backgrounded function to exit?

(zenity uses the echo values of 25,50,75 and 99 to change the "complete-ness" of the progress bar. and the echo "# (whatever)" to change the text.)

all it is is something to tell him "Hey Duffus im bussy copying stuff dont shut me off"

i have tried Guss's solution: (seperated script for test purposes)

#!/bin/bash

transtest ()
{

i2="Im Working On It!"

var=0
trap 'var=1' 2

(
while [ "$var" = 0 ]; do
# =================================================================
echo "25"
echo "# ^_^ $i2" ; sleep 1

# =================================================================
echo "50"
echo "# ^-^ $i2" ; sleep 1

# =================================================================
echo "75"
echo "# ^_^ $i2" ; sleep 1

# =================================================================
echo "99"
echo "# ^-^ $i2" ; sleep 1


done
echo "# Your files are DONE TRANSFERING!" ; sleep 1
echo "100"
) |
zenity --progress \
  --title="File Transfer In Progress" \
  --text="# Im Getting Warmed Up" \
  --percentage=0 \
  --auto-close \
  --auto-kill
}

transtest &
pid=$!
echo "$pid"
sleep 20 #code to copy? or just for this test?
echo "Killing Child: $pid"
kill -2 "$pid"
echo "wait"
wait
exit

i see result from echo "wait" and it still keeps looping.

2 Answers 2

2

The problem you have is caused by the subshell spawned when you call transprog to run in the background (what the & symbol does). You have to understand that your bash script is not a program but a shell script which behaves according to the shell rules - a shell can only run one process at a time and wait for it to terminate.

The "jobs system" allows you to run another process and not wait for it, but when that process is a function defined in bash, bash will spawn a "sub shell" that "copies" all the variables and functions of the original shell, and track that as a job. The copied shell isn't aware of changes made it its parent shell - because it is not referencing the parent shell variables directly - just a copy of them.

What you need to do for your background loop, is to use an Inter Process Communication mechanism (IPC) to let the parent shell notify the loop in the subshell to abort. For such a simple signaling, usually a "POSIX Signal" is appropriate.

The most appropriate signal to use is the "interrupt signal", SIGINT (signal number 2), which is what is produced when you press CTRL+C and that normally stops your script. But we can catch this signal and update a local variant using the trap built-in command.

Here is a simple example:

#!/bin/bash

function test() {
  var=0
  trap 'var=1' 2
  while [ "$var" = 0 ]; do
    sleep 3
    echo 'still sleeping'
  done
  echo "done sleeping"
}

test &
pid=$!
echo $pid;
sleep 5
echo "Killing $pid"
kill -2 "$pid"
wait

This will output:

$ ./test.sh 
8359
still sleeping
Killing 8359
still sleeping
done sleeping

What happens here is that after 5 seconds there loop has completed one round and is now sleeping on the second round when the parent uses the kill builtin to signal the subshell. The subshell's sleep is interrupted, it receives the signal and updates the variable, then resumes the sleep When the loop awakens, it outputs the text then goes back and sees that the var has changed and leaves the loop.

I use the wait builtin in the parent just to make sure the parent process doesn't exit before the subshell. It essentially "waits" for the subshell to end.

[UPDATE] After looking at the OP a bit more, there's a few more subshells going on, and I don't want to fight that - subshells (in addition to functions) is a good way to manage complexity in a script - but that does make finding the correct PID to send the signal to a bit difficult.

Instead we can use another IPC mechanism called a "shared memory segment". This allows a process to allocate a batch of memory, assign it a name, and let multiple processes access the same memory (it is covered in the IPC document linked above, in greater detail). While in Bash we can't directly address that feature (unlike what a C program, for example, can do), we can use a Linux mechanism that exposes the shared memory IPC to any program through the /dev/shm device category. So the OP script might look something like this:

#!/bin/bash

transtest () {
  local sync="$1"
  i2="Im Working On It!"
  var=0
  (
    while [ -z "$(cat $sync)" ]; do sleep 1; done
    echo "# Your files are DONE TRANSFERING!"
    sleep 1
    echo "100"
  ) | zenity --progress --title="File Transfer In Progress" --text="# Im Getting Warmed Up" \
    --percentage=0 --pulsate --auto-close --auto-kill
}

sync=/dev/shm/syntest-$$ # allocate a shared memory segment name for this process
echo '' > $sync # init the shm
transtest $sync &
sleep 20
echo "Signaling done"
echo "done" > $sync
echo "waiting"
wait
# remove the shm segment. While not actually necessary (it will clear
# when you reboot), I like to keep a clean system
rm $sync 
exit

Basically, we treat the SHM segment as a file, and just store some data there. The loop keeps reading the "file" and when there is content there it breaks out.

One question remains, why not just allocate a temp file (using mktemp) and use that for syncing? Well - that would work almost the same! The main difference, and why I prefer SHM for that kind of thing, is that its not really a file and isn't subject the file system IO operations, disk errors and the likes, and if I fail to clean it up, it will be gone the next time the system boots.

9
  • So lets see if this just clicked into the right spot... the kill -2 "$pid" signals the subshell to restart the function and trap 'var=1' 2 gets the new value of copystat=0? Feb 4, 2016 at 8:31
  • not "restart" - it sends a notification (a "signal") to the other process that there is something that it needs to know. The kernel delivers the signal by stopping the process, hitting its "signal handler" and then letting it resume what it was doing from the exact spot it left. Some signals have meanings and predefined behaviors (that you can override as we did here), some are completely up to the programmer to decide what they mean, e.g. USR1 and USR2.
    – Guss
    Feb 4, 2016 at 8:37
  • BTW "pid" is an accepted shorthand for "process ID", if it wasn't clear.
    – Guss
    Feb 4, 2016 at 8:38
  • Thanks for the Tip on pid! so how would i implement this Strategy into the code i currently have? Feb 4, 2016 at 8:49
  • so i have set up this example: #!/bin/bash transtest () { i2="Im Working On It!" var=0 trap 'var=1' 2 ( while [ "$var" = 1 ]; do # redacted (see OP) done echo "# DONE!" ; sleep 1 echo "100" ) | zenity --progress \ --title="File Transfer In Progress" \ --text="# Im Getting Warmed Up" \ --percentage=0 \ --auto-close \ --auto-kill } transtest & pid=$! echo "$pid" sleep 20 echo "Killing Child: $pid" kill -2 "$pid" wait exit when it runs i see it echo "killing Child: 2408" and nothing happens. i gave it a full minute, still nothing. Feb 4, 2016 at 9:17
1

You have changed the value of conditional variable (copystat) outside the while loop (running in a subshell), which is outside the scope of the while loop. Unless you have some condition inside the loop itself to trigger the loop exit condition or use an IPC, the loop will continue forever.

So the solution is to incorporate the copying or any other conditional logic within the while loop itself to trigger the exit Or you can think about changing the design altogether to make it more robust in such situations Or use an IPC mentioned here.

10
  • would it work if i used the while to execute cp in the condition statement? Feb 4, 2016 at 7:52
  • @Desert_foxhole Yes, it would ..but whats the point of a while loop unless you are sure of doing somwthing "loop"-y..if it is just a one time run you can use if like: if cp -rf "$netlocdirFIN/" "$transdir2/" (similarly while cp -rf "$netlocdirFIN/" "$transdir2/" or reverse way until cp -rf "$netlocdirFIN/" "$transdir2/")
    – heemayl
    Feb 4, 2016 at 7:55
  • i tries out those options and from what i can tell the while loop actually executes the cp command before it gets yo the copy aspect. @Guss i understand that & will split off of the parent shell, but i had no idea that it went as far as to start en entirly different process. (thank you for that) one of the reasons that i have that while loop is because the echo values are (sorry if i have the wrong term) being "piped" to zenity in order to update the "progress" window. is there a way i can fix this so that cp executes without transprog having to wait for it? Feb 4, 2016 at 8:15
  • @Desert_foxhole the while loop will be based on the exit status of cp command..so cp will always be executed first and the while loop will start or stop based on the success/failure of cp....its not clear to me what you want eventually but i think you really need to rethink about the algorithm..
    – heemayl
    Feb 4, 2016 at 8:19
  • @Desert_foxhole add the content of the "code for loop" part. Perhaps we can think of a better way to handle this.
    – muru
    Feb 4, 2016 at 8:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .