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?
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.
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.
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.
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.
There are three steps involved in solving your problem:
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.
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.
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.
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 :)
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With