5

I'm running Ubuntu 16.04, using a terminal emulator.

I have two scripts: a.sh and b.sh.  a.sh calls b.sh.

a.sh

echo "a is running"
sleep 5s
bash b.sh
sleep 5s
echo "a is still running"

b.sh

echo "b is now running"
sleep 1000s
echo "Bananas"

Is there a way to run a.sh, stop b.sh before it completes (before it prints Bananas) and let a.sh run to completion and print "a is still running"?

I have tried putting pkill -f b.sh inside a.sh, but both a.sh and b.sh stop before a.sh completes.

  • 3
    What happened when you tried pkill -f b.sh within the a.sh script? – Kusalananda Feb 3 '18 at 20:58
  • What @Kusalananda says – k.Cyborg Feb 3 '18 at 21:14
  • Depending on platform, I either get your scripts working just fine, or failing because b.sh has already finished before the pkill line runs. I don't really think either of those outcomes is wrong, but it does mean that what works is going to depend on which platform you're targeting. That said, I don't think I understand what you're trying to do or trying to test and I suspect that your test script doesn't really represent what you want. – Michael Homer Feb 3 '18 at 22:32
  • It would be helpful to edit in the message you get/a transcript of you running the commands. – Michael Homer Feb 3 '18 at 22:34
  • Interesting puzzle that you created! – G-Man Feb 4 '18 at 7:36
4

At one point, you were close to what you seem to want.

a.sh

#!/bin/sh

echo "a.sh running"
sleep 10s
(sleep 10s; pkill -f b.sh) &
bash b.sh
sleep 10s
pkill -f b.sh
echo "a.sh still running"

b.sh    (same as your version)

echo "b is now running"
sleep 1000s
echo "Bananas"

The (sleep 10s; pkill -f b.sh) & line in a.sh creates a subshell that sleeps for ten seconds and then kills b.sh.  It then puts that subshell into the background, so a.sh can keep running.  a.sh then runs b.sh, which runs sleep 1000s.  Ten seconds later, the sleep 10s in the subshell finishes, and it (the subshell) runs pkill -f b.sh, killing the b.sh process.  a.sh then resumes running.  The second pkill -f b.sh does nothing, since the b.sh process is already terminated.

This is what happens when it runs:

$ ./a.sh
a.sh running                                              # immediately
b is now running                                          # about 10 seconds after the above
./a.sh: line 6: 13684 Terminated            bash b.sh     # about 10 seconds after the above
a.sh still running                                        # about 10 seconds after the above
$                                                         # immediately after the above

This has the advantage that a.sh can resume running immediately if b.sh finishes quickly.  Only if b.sh runs for more than 10 seconds does the pkill kick in.  In the other answer, a.sh has to sit idle while the sleep before the pkill takes up time, even if b.sh has already wrapped up.


Another user posted a comment (now deleted) saying

I get

./a.sh
a.sh running
b is now running
Terminated

not what you report. Any idea?

It was a dark and stormy night.  Suddenly, a shot rang out!

        It Was a Dark and Stormy Night, Snoopy by Charles M. Schulz

The argument to pgrep and pkill is a regular expression; specifically, an Extended Regular Expression1.  Try running cat and cut simultaneously2.  Then do pgrep c.t — it will list two PIDs, and you can confirm with ps that they are cat and cut

If you had done exactly what I said, you really should have gotten the same results I did.  But I’m willing to wager 42 Zorkmids that you did something different.  You either:

  1. Began a.sh with #!/bin/bash as a she-bang (first line) instead of #!/bin/sh, or
  2. Ran a.sh with bash a.sh instead of ./a.sh.

Suddenly, a shot rang out!

b.sh is a regular expression that matches b followed by any character followed by sh.  It matches itself (b.sh), and also things like b!sh, b~sh, b&sh, b7sh, and bush.  And … … (wait for it) … … it also matches bash!

So pkill -f b.sh kills all bash processes, and also any others that have b.sh in their names.  When I ran what I ran, my login shell (which is at least partially immune to pkill) was bash, but a.sh was running under /bin/sh (but b.sh was running under bash).  The pkill -f b.sh command killed the bash b.sh process because it contained both bash and b.sh.  But the /bin/sh that was running a.sh was unaffected.

Assuming you ran it the way I believe you ran it (either of the two alternatives), all the script processes were running bash.  And so the pkill -f b.sh command killed all the script processes.

Change pkill -f b.sh to pkill -f 'b\.sh' (escaping the dot so it matches only a dot, and not any character), and you should get the right results.
________
1 pgrep(1)
2 If you have two terminals (shell windows), run cat | cut -f1 (with input from the terminal) in one of them, and the p-commands in the other.  If you have only one terminal, run sleep 42 | cat | cut -f1 & (without the sleep, the pipeline would collapse immediately), and then run p-commands in the same window.

2

The line:

bash b.sh

will wait for the b.sh script to finish, there is no way to do anything in the script before b.sh ends because the a.sh does not have the control.

To have the b.sh script go to background and give control back to a.sh is to use a like similar to:

bash b.sh &

That returns the control to a.sh immediately and we can have this kind of script:

#!/bin/bash 

echo "a is running"
sleep 1s
bash b.sh &
sleep 2
pkill -f 'b\.sh'
sleep 1s
echo "a is still running"

While script b.sh keeps being:

#!/bin/bash
echo "b is now running"
sleep 1000s
echo "Bananas"

On execution you will get:

$ ./a.sh
a is running
b is now running
./a.sh: line 7:  8424 Terminated              bash b.sh
a is still running

To avoid the "Job control message" use a sub-shell, change bash b.sh & to:

(bash b.sh &)

And, on execution, you will get:

$ ./a.sh
a is running
b is now running
a is still running

Edit:

The problem with a 'Terminated' response you report is that the pkill command you posted pkill -f b.sh is using the argument as a regex. That regex match b followed by any character . followed by sh. That match the name 'bash' which is the program started to run also a.sh, that explain why a.sh is being terminated. Change the pkill command to:

pkill -f 'b\.sh'

to ensure that the matched regex is a plain string 'b.sh'.

You can see the processes that the pkill would match by placing this:

ps -f -o pid,cmd,args |grep 'b.sh'

just before the pkill.

Change it to grep 'b\.sh' to find out that it actually match only one pid.

  • Thanks - but it didn't work - I got 'Terminated' after 'b is now running' - b.sh must have stopped - a.sh seemed to stop before completion - never got 'a is still running' - same thing that happened with my above scripts. Any other suggestions? – speld_rwong Feb 4 '18 at 4:12
  • Added an edit to explain why you get a Terminated: quote 'b\.sh' to avoid this pattern to match bash. @speld_rwong – Isaac Feb 4 '18 at 6:28
0

Your a.sh script must run the b.sh script as a background job to be able to terminate it prematurely.

Since b.sh is run as a background job, you have access to its process ID (PID), which means you may use kill to signal it. This is safer than using pkill which will potentially signal other processes that matches the pattern that you're giving to it.

You could do the following in a.sh:

#!/bin/sh

echo "A: I'm alive!"
./b.sh & bpid=$!

echo 'A: Sleeping for a short while'
sleep 2

echo 'A: Signalling B'
kill "$bpid"

echo "A: I'm done here"

The $! will expand to the PID of the most recently started background task.

Example of b.sh:

#!/bin/sh

echo "B: I'm alive!"

echo 'B: Sleeping for longer than A does'
sleep 10

echo "B: I'm done here"

Running it:

$ ./a.sh
A: I'm alive!
A: Sleeping for a short while
B: I'm alive!
B: Sleeping for longer than A does
A: Signalling B
A: I'm done here

Note that b.sh may not actually terminate until its sleep call is done. This is the case with bash, dash, zsh and pdksh, but not with ksh93 (with which b.sh terminates immediately).

-2

I have used below method. Added one extra line in b.sh which makes to work as per requirement

script a.sh will be the same

script a.sh

echo "a is running"
sleep 5s
bash b.sh
sleep 5s
echo "a is still running"

=====================================================

script b.sh

echo "b is now running"
sleep 20s

exit

==>exit command will come out of the script

echo "Bananas"

output

   a is running
b is now running
a is still running
  • You still are required to quote the regex to grep: | grep -i 'b\.sh' | or you run the risk of calling kill on a bash shell. – Isaac Feb 4 '18 at 17:21
  • So, rather than having a.sh stop b.sh, as the OP seems to want, you propose having b.sh stop itself? You know there’s a much shorter command that does something similar: exit.  Why not just put an exit command in the middle of b.sh? – G-Man Feb 4 '18 at 18:41

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.