I need two ways to terminate a part of my bash script.
Either a counter reaches a predefined number, or the user manually forces the script to continue with whatever the value the counter currently has.
Specifically – I'm listing USB drives. If there is 15 of them, the function that counts them exits and the script can continue.
My code looks a bit like this:
scannew(){
NEW=0
OLD=$NEW
while [ true ]; do
# count the new drives
lsblk -r > drives.new
diff drives.old drives.new | grep disk | cut -d' ' -f 2 | sort > drives.all
NEW=$(wc -l drives.all | cut -d' ' -f1)
echo -en " Detected drives: $NEW \r"
sleep 0.01
if [ "$NEW" -eq "15" ]; then # exit if we reach the limit
break
fi
done
}
# SOME CODE...
lsblk -r > drives.old
scannew & # start live device counter in the background
SCAN_PID=$! # remember it's PID
wait $SCAN_PID 2>/dev/null # wait until it dies
echo "It's on!"
# REST OF THE CODE...
I tried various stuff with the read
command, but the result is, the script will always wait for read to exit (after pressing ENTER) and I can't make the "15 limit" condition to override that.
For example I tried using read -t
instead of sleep
in the scannew()
function:
scannew(){
NEW=0
OLD=$NEW
while [ true ]; do
# count the new drives
lsblk -r > drives.new
diff drives.old drives.new | grep disk | cut -d' ' -f 2 | sort > drives.all
NEW=$(wc -l drives.all | cut -d' ' -f1)
echo -en " Detected drives: $NEW \r"
read -t 0.01 -n 1 && break # read instead of sleep
if [ "$NEW" -eq "15" ]; then
break
fi
done
}
However – it seems that the function subprocess doesn't have access to stdin, and using read -t 0.01 -n 1 < /dev/stdin && break
instead didn't work either.
How can I make this work?
Best Answer
Let me start by saying, you could just inline all the stuff you have in
scannew
, since you'rewait
ing anyway, unless you intend to scan again at some other point in your script. It's really the call towc
that you're concerned might take too long, which, if it does, you can just terminate it. This is a simple way to set that up usingtrap
which allows you to capture signals sent to a process and set your own handler for it:However, if you want to retain the way you do things, with
scannew
being a function run as a background job, it takes a bit more work.Since you want user input, the proper way to do it is to use
read
, but we still need the script to go on ifscannew
completes and not just wait for user input forever.read
makes this a bit tricky, becausebash
waits for the current command to complete before allowingtrap
s to work on signals. The only solution to this that I know of, without refactoring the entire script, is to putread
in awhile true
loop and give it a timeout of 1 second, usingread -t 1
. This way, it'll always take at least a second for the process to finish, but that may be acceptable in a circumstance like yours where you essentially want to run a polling daemon that lists usb devices.Of course, if what you actually want is a daemon that watches for changes in the number of usb devices or something, you should look into
systemd
which might provide something more elegant.