Shell Script: kill program after set time if output file is empty - bash

I'm running a command from within terminal that goes through a directory checking for media files and then extracts closed captioning data from those files. Unfortunately, even if there is no closed captioning data present, the program still processes the entire file and this can take a long time. What I would like to be able to do is check the output after 60 seconds and look for data, and if the file is empty, terminate the process and move on to the next file.
My old command is as follows
for i in */*.vob
do
/home/me/ccextractor/linux/ccextractor -out=srt -utf8 -trim "$i"
done
I've been experimenting with sleep but I can't seem to get it working. Any suggestions?
SOLUTION
With help from the answers below (take note of the comments as well), my final working code is:
for i in */*.vob
do
/home/me/ccextractor/linux/ccextractor -out=srt -utf8 -trim "$i" &
pid=$!
sleep 15
srtfile=$(expr "${i}" | sed -r 's/.{4}$//')
fgrep -q "1" "${srtfile}.srt" || kill $pid
wait $pid
done

You can have CCExtractor exit after processing one minute of video, there's not need to kill the process or control it separately.
ccextractor -endat 1:00 [etc]
will do what you want.

Assuming that ccextractor works fine in the background (doesn't require input or a tty), then try:
for i in */*.vob
do
/home/me/ccextractor/linux/ccextractor -out=srt -utf8 -trim "$i" &
pid=$!
sleep 60
[ -s "${i}_1" ] || kill $pid
wait $pid
done
where I have also assumed that the output file that ccextractor creates has _1 appended to the name of the input file.
[ -s "${i}_1" ] tests to see if the output file exists and has size greater than zero. If that is false, then the "or" condition is run and the process is killed.
wait $pid causes the shell to wait for one ccextractor to exit before starting another. If you want to run then all in parallel, remove this line.

Related

How to stop the execution of a bash command after N seconds?

I am writing a simple bash script that analyses the contents of a directory and invokes a specific command for each file, however, in some cases, the execution takes longer than expected, and I would like to stop it after a certain number of seconds (or minutes) to move on to parsing the next entry.
For now, the script works in the following way:
#!/bin/bash
for f in /home/Users/Desktop/fdroid_tests/destination_dir/*; do
if [ -d "$f" ]; then
qark --java $f
fi
done
How can I solve it?
Besides just backgrounding the tasks, you may also be interested in the timeout command. For example timeout 5s qark --java $f will run your qark application and signal it to exit after 5 seconds if it hasn't already exited.
You can combine the two answers as well (timeout 5s qark --java $f &) if it's okay to have all your instances of qark run simultaneously. If you do, you may also want to add a wait after the loop so that the script doesn't exit until all the qark instances finish or timeout.
In bash, it is possible to launch a process in background, using the ampersand switch:
qark --java $f &
You wait for five seconds:
sleep 5
You stop all running qark processes, using pkill:
pkill qark
(I didn't try this, but you can give it a try.)
Edit:
According to the added comments to this answer, this might even be better:
qark --java $f & pid=$!
sleep 5
kill $pid

Can't terminate command from a different process

I have a command "command1" that runs indefinitely (must be killed with Ctrl+c), and that at random intervals outputs new lines to stdout. My goal is to run it and see if it outputs a certain "target" line within 10 seconds. If the target output is generated, stop immediately with success, otherwise wait for the 10 seconds and fail.
I came up with this:
timeout 10 bash -c '(while read line; do [[ "$line" == "target" ]] && break; done < <(command1))'
It works, but the problem is that when a match is found, although the timeout command completes and returns successfully, command1 will continue to run indefinitely as a background process. I need it to stop as well when "break" is executed. If a match is not found, and the timeout expires, command1 is stopped correctly.
I also tried this:
timeout 10 bash -c '(command1 | while read line; do [[ "$line" == "target" ]] && exit; done)'
Which does not leave any spurious processes running. The problem is that the exit command does not terminate command1 since it is in a separate process, and the timeout always expires even if the target is found before.
I was exploring some alternative options, such as wait -n, but the same problem persists, and I must use bash 4.2, so wait -n isn't even an option.
Any suggestions would be greatly appreciated.
When command1 does not terminate itself, you can kill it manually.
By the way: Instead of while read ... you can use grep.
timeout 10 bash -c 'command1 | (grep -m1 -Fx "target"; pkill -P $PPID command1)'
-P $PPID ensures that only the command1 from this command is killed, and not some other command1 that might run in another shell at the same time.
This assumes that command1 is a single command, and not something like (cmd1; cmd2; ...). For that case, you could simply kill the whole bash process using kill $PPID.
Found what works best for my case:
timeout 10 bash -c 'grep -q -m1 "target" <(command1); pkill -P $!'
All processes terminate gracefully when either the target is found or the timeout expires. If found, command returns 0, if not found, command returns 124.
Thank you #Socowi for some very helpful hints that put me on the right track.

Running commands in bash script in parallel with loop

I have a script where I start a packet capture with tshark and then check whether the user has submitted an input text file.
If there is a file present, I need to run a command for every item in the file through a loop (while tshark is running); else continue running tshark.
I would also like some way to stop tshark with user input such as a letter.
Code snippet:
echo "Starting tshark..."
sleep 2
tshark -i ${iface} &>/dev/null
tshark_pid=$!
# if devices aren't provided (such as in case of new devices, start capturing directly)
if [ -z "$targets" ]; then
echo "No target list provided."
else
for i in $targets; do
echo "Attempting to deauthenticate $i..."
sudo aireplay-ng -0 $number -a $ap -c $i $iface$mon
done
fi
What happens here is that tshark starts, and only when I quit it using Ctrl+c does it move on to the if statement and subsequent loop.
Adding a & at the end of command executes the command in a new sub process. Mind you won't be able to kill it with ctlr + c
example:
firefox
will block the shell
firefox & will not block shell

Bash wait terminates immediately?

I want to play a sound after a command finishes, but only if the command took more than a second.
I have this code (copied from https://stackoverflow.com/a/11056286/1757964 and modified slightly):
( $COMMAND ) & PID=$!
( sleep 1; wait -f $PID 2>/dev/null; paplay "$SOUND" ) 2>/dev/null & WATCH=$!
wait -f $PID 2>/dev/null && pkill -HUP -P $WATCH
The "wait" on the second line seems to terminate immediately, though. I tried the -f flag but the behavior didn't change. How can I make the "wait" actually wait?
The problem is that you're running wait in a subshell. A shell can only wait for its own children, but the process you're trying to wait for is a sibling of the subshell, not a child.
There's no need to use wait for this. The question you copied the code from is for killing a process if it takes more than N seconds, not for telling how long a command took.
You can use the SECONDS variable to tell if it took more than a second. This variable contains the number of seconds since the shell started, so just check if it has increased.
start=$SECONDS
$COMMAND
if (($SECONDS > $start))
then
paplay "$SOUND"
fi
I'd probably want to streamline this to capture the data (in # of seconds since epoch) and then compare the difference (and if > 1 second then play sound), eg:
prgstart=$(date '+%s') # grab current time in terms of # of seconds since epoch
$COMMAND # run command in foreground => no need for sleep/wait/etc; once it completes ...
prgend=$(date '+$s') # grab current time in terms of # of seconds since epoch
if [[ $(( ${prgend} - ${prgstart} )) -gt 1 ]]
then
paplay "$SOUND"
fi

Sleep seems to run first in bash script

I need to terminate a script if it exceeds a specific duration (10 mins)
examplescript.sh &
pid=$!
sleep 600
if ['pgrep $pid']
then
kill $pid
fi
When I tested it on my test environment, it seems working well. examplescript.sh runs first and if it runs for more than 10 mins, it will be terminated. However, when I tried in our production environment, it seems that sleep runs first. It waits 600s before running the examplescript.sh. Is there something wrong in the script?
There is multiply thing you should correct in your code.
pgrep will make a regex search on process names not pids. You can use kill -0 pid to check if a process with pid is running.
[ (test) is a command[1] and should be treated as one. That means each argument should be separated by spaces. When using [ the last argument should also be ]:
[ arg1 arg2 ]
In your example you wont need [ since kill -0 will exit truly if the process is still running:
if kill -0 pid; then
And to wrap it up:
examplescript.sh &
pid=$!
sleep 600
if kill -0 "$pid" 2> /dev/null; then
kill "$pid"
fi
kill -0 will write an error to stderr if the process is not running anymore. So we redirect that to /dev/null.
[1] It's usually a build-in these days.
Another thing to note is that your script will run for 600 seconds even though examplescript.sh will only take a few seconds to run.
Are your production machines significantly faster? I do not have example script to really run this on my machine, but I think your problem might be solved if you take the code you mention above
examplescript.sh &
pid=$!
sleep 600
if ['pgrep $pid']
then
kill $pid
fi
put it in a file called, say, monitor.sh and run that file in the background. i.e.
monitor.sh &
Hope this helps.

Resources