Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

executing bash loop while command is running

Tags:

bash

shell

I want to build a bash script that executes a command and in the meanwhile performs other stuff, with the possibility of killing the command if the script is killed. Say, executes a cp of a large file and in the meanwhile prints the elapsed time since copy started, but if the script is killed it kills also the copy. I don't want to use rsync, for 2 reasons: 1) is slow and 2) I want to learn how to do it, it could be useful. I tried this:

until cp SOURCE DEST
do
#evaluates time, stuff, commands, file dimensions, not important now
#and echoes something
done

but it doesn't execute the do - done block, as it is waiting that the copy ends. Could you please suggest something?

like image 923
marco Avatar asked Nov 23 '13 16:11

marco


People also ask

How do you do a while loop in bash?

How to do a do-while loop in bash? There is no do-while loop in bash. To execute a command first then run the loop, you must either execute the command once before the loop or use an infinite loop with a break condition.

Can you edit Bash script while running?

The shell reads and executes commands one by one, so the simple answer is "yes, editing a running script can affect it", but it's important to understand the details. If you are careful, you can add lines to the end of a running script, and they will be run after the other commands already in the script.

Does a Bash script stop if a command fails?

Bash scripts are a great way to run multiple commands consecutively. When writing a script, we have to remind ourselves that if one command fails, all subsequent commands are still executed.


4 Answers

until is the opposite of while. It's nothing to do with doing stuff while another command runs. For that you need to run your task in the background with &.

cp SOURCE DEST &
pid=$!

# If this script is killed, kill the `cp'.
trap "kill $pid 2> /dev/null" EXIT

# While copy is running...
while kill -0 $pid 2> /dev/null; do
    # Do stuff
    ...
    sleep 1
done

# Disable the trap on a normal exit.
trap - EXIT

kill -0 checks if a process is running. Note that it doesn't actually signal the process and kill it, as the name might suggest. Not with signal 0, at least.

like image 54
John Kugelman Avatar answered Nov 15 '22 14:11

John Kugelman


There are three steps involved in solving your problem:

  1. Execute a command in the background, so it will keep running while your script does something else. You can do this by following the command with &. See the section on Job Control in the Bash Reference Manual for more details.

  2. Keep track of that command's status, so you'll know if it is still running. You can do this with the special variable $!, which is set to the PID (process identifier) of the last command you ran in the background, or empty if no background command was started. Linux creates a directory /proc/$PID for every process that is running and deletes it when the process exits, so you can check for the existence of that directory to find out if the background command is still running. You can learn more than you ever wanted to know about /proc from the Linux Documentation Project's File System Hierarchy page or Advanced Bash-Scripting Guide.

  3. Kill the background command if your script is killed. You can do this with the trap command, which is a bash builtin command.

Putting the pieces together:

# Look for the 4 common signals that indicate this script was killed.
# If the background command was started, kill it, too.
trap '[ -z $! ] || kill $!' SIGHUP SIGINT SIGQUIT SIGTERM
cp $SOURCE $DEST &  # Copy the file in the background.
# The /proc directory exists while the command runs.
while [ -e /proc/$! ]; do
    echo -n "."  # Do something while the background command runs.
    sleep 1  # Optional: slow the loop so we don't use up all the dots.
done

Note that we check the /proc directory to find out if the background command is still running, because kill -0 will generate an error if it's called when the process no longer exists.


Update to explain the use of trap:

The syntax is trap [arg] [sigspec …], where sigspec … is a list of signals to catch, and arg is a command to execute when any of those signals is raised. In this case, the command is a list:

'[ -z $! ] || kill $!'

This is a common bash idiom that takes advantage of the way || is processed. An expression of the form cmd1 || cmd2 will evaluate as successful if either cmd1 OR cmd2 succeeds. But bash is clever: if cmd1 succeeds, bash knows that the complete expression must also succeed, so it doesn't bother to evaluate cmd2. On the other hand, if cmd1 fails, the result of cmd2 determines the overall result of the expression. So an important feature of || is that it will execute cmd2 only if cmd1 fails. That means it's a shortcut for the (invalid) sequence:

if cmd1; then
   # do nothing
else
   cmd2
fi

With that in mind, we can see that

trap '[ -z $! ] || kill $!' SIGHUP SIGINT SIGQUIT SIGTERM

will test whether $! is empty (which means the background task was never executed). If that fails, which means the task was executed, it kills the task.

like image 31
Adam Liss Avatar answered Nov 15 '22 12:11

Adam Liss


here is the simplest way to do that using ps -p :

[command_1_to_execute] &
pid=$!

while ps -p $pid &>/dev/null; do
    [command_2_to_be_executed meanwhile command_1 is running]
    sleep 10 
done

This will run every 10 seconds the command_2 if the command_1 is still running in background .

hope this will help you :)

like image 32
Nick Vladiceanu Avatar answered Nov 15 '22 13:11

Nick Vladiceanu


What you want is to do two things at once in shell. The usual way to do that is with a job. You can start a background job by ending the command with an ampersand.

copy $SOURCE $DEST & 

You can then use the jobs command to check its status.

Read more:

  • Gnu Bash Job Control
like image 38
C. Ross Avatar answered Nov 15 '22 12:11

C. Ross