Run a command in bash and keep it running until the user choose the no option - bash

How do I make this work? I'm really trying to learn on my own.
But I'm really not getting this. I want to run a command in the
background and prompt a user whether they would like it to keep running
or not. If not, kill the command. I know I'm missing something. I'm just
not sure what.
Here's my code:
command > /dev/null 2>&1 &
echo
until [[ $REPLY =~ ^[Yy]$ ]] ;
do
echo
read -p "Would you like to stop the command [Yy/Nn]? " -n 1 -r
# Stop stop the command
killall -9 command
done

It is a little unclear what you want your loop frequency to be, but presuming you just want to block until the user types Yy, you can simply start command in the background, save its process ID (e.g. PID) using the bash special variable $!. Then simply loop until you get a Yy (which in bash, you can simply use the parameter expansion ${var,,} to evaluate var in lower-case). Upon received a Yy answer (using ans as the variable to store the response), you could do something similar to:
#!/bin/bash
command & >/dev/null 2>&1 # start command in background
cmdpid=$! # save the PID of command
ans=
while :; do # infinite loop until y or Y
printf "\nWould you like to stop the command (y/n)? "
read -n 1 -r ans # read ans
[ "${ans,,}" = 'y' ] && break
done
printf "\nkilling proc: %d\n" "$cmdpid"
kill $cmdpid # kill the PID
Example Use/Output
(without actually starting command above (as I have no clue what it is))
$ bash rununtil.sh
Would you like to stop the command (y/n)? k
Would you like to stop the command (y/n)? i
Would you like to stop the command (y/n)? l
Would you like to stop the command (y/n)? l
Would you like to stop the command (y/n)? Y
killing proc
Look things over and let me know if you have further questions.

Related

Kill bash command when line is found

I want to kill a bash command when I found some string in the output.
To clarify, I want the solution to be similar to a timeout command:
timeout 10s looping_program.sh
Which will execute the script: looping_program.sh and kill the script after 10 seconds of execute.
Instead I want something like:
regexout "^Success$" looping_program.sh
Which will execute the script until it matches a line that just says Success in the stdout of the program.
Note that I'm assuming that this looping_program.sh does not exit at the same time it outputs Success for whatever reason, so simply waiting for the program to exit would waste time if I don't care about what happens after that.
So something like:
bash -e looping_program.sh > /tmp/output &
PID="$(ps aux | grep looping_program.sh | head -1 | tr -s ' ' | cut -f 2 -d ' ')"
echo $PID
while :; do
echo "$(tail -1 /tmp/output)"
if [[ "$(tail -1 /tmp/output)" == "Success" ]]; then
kill $PID
exit 0
fi
sleep 1
done
Where looping_program.sh is something like:
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Success"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
echo "Fail"
sleep 1;
But that is not very robust (uses a single tmp file... might kill other programs...) and I want it to just be one command. Does something like this exist? I may just write a c program to do it if not.
P.S.: I provided my code as an example of what I wanted the program to do. It does not use good programming practices. Notes from other commenters:
#KamilCuk Do not use temporary file. Use a fifo.
#pjh Note that any approach that involves using kill with a PID in shell code runs the risk of killing the wrong process. Use kill in shell programs only when it is absolutely necessary.
There are more suggestions below from other users, I just wanted to make sure no one came across this and thought it would be good to model their code after.
looping_program() {
for i in 1 2 3; do echo $i; sleep 1; done
echo Success
yes
}
coproc looping_program
while IFS= read -r line; do
if [[ "$line" =~ Success ]]; then
break
fi
done <&${COPROC[0]}
exec {COPROC[0]}>&- {COPROC[1]}>&-
kill ${COPROC_PID}
wait ${COPROC_PID}
Notes:
Do not use temporary file. Use a fifo.
Do not use tail -n1 to read last line. Read from the stream in a loop.
Do not repeat tail -1 twice. Cache the result.
Wait for pid after killing to synchronize.
When you're using a coprocess, use COPROC_PID to get the PID
When you're not using a coprocess, use $! to get the PID of a background process started from the current shell.
When you can't use $! (because the process you're trying to get a PID of was not spawned in the background as a direct child of the current shell), do not use ps aux | grep to get the pid. Use pgrep.
Do not use echo $(stuff). Just run the stuff, no echo.
With expect
#!/usr/bin/env -S expect -f
set timeout -1
spawn ./looping_program.sh
expect "Success"
send -- "\x03"
expect eof
Call it looping_killer:
$ ./looping_killer
spawn ./looping_program.sh
Fail
Fail
Fail
Success
^C
To pass the program and pattern:
./looping_killer some_program "some pattern"
You'd change the expect script to
#!/usr/bin/env -S expect -f
set timeout -1
spawn [lindex $argv 0]
expect -- [lindex $argv 1]
send -- "\x03"
expect eof
Assuming that your looping program exists when it tries to write to a broken pipe, this will print all output up to and including the 'Success' line and then exit:
./looping_program | sed '/^Success$/q'
You may need to disable buffering of the looping program output. See Force line-buffering of stdout in a pipeline and How to make output of any shell command unbuffered? for ways to do it.
See Should I save my scripts with the .sh extension? and Erlkonig: Commandname Extensions Considered Harmful for reasons why I dropped the '.sh' suffix.
Note that any approach that involves using kill with a PID in shell code runs the risk of killing the wrong process. Use kill in shell programs only when it is absolutely necessary.

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

Input to proprietary command prompt in shell script

I want to know how we can provide inputs to command prompts which change. I want to use shell scripting
Example where '#' is usual prompt and '>' is a prompt specific to my program:
mypc:/home/usr1#
mypc:/home/usr1# myprogram
myprompt> command1
response1
myprompt> command2
response2
myprompt> exit
mypc:/home/usr1#
mypc:/home/usr1#
If I understood correctly, you want to send specific commands to your program myprogram sequentially.
To achieve that, you could use a simple expect script. I will assume the prompt for myprogram is noted with myprompt>, and that the myprompt> symbol does not appear in response1 :
#!/usr/bin/expect -f
#this is the process we monitor
spawn ./myprogram
#we wait until 'myprompt>' is displayed on screen
expect "myprompt>" {
#when this appears, we send the following input (\r is the ENTER key press)
send "command1\r"
}
#we wait until the 1st command is executed and 'myprompt>' is displayed again
expect "myprompt>" {
#same steps as before
send "command2\r"
}
#if we want to manually interract with our program, uncomment the following line.
#otherwise, the program will terminate once 'command2' is executed
#interact
To launch, simply invoke myscript.expect if the script is in the same folder as myprogram.
Given that myprogram is a script, it must be prompting for input with something like while read IT; do ...something with $IT ...;done . Hard to say exactly how to change that script without seeing it. echo -n 'myprompt> would be the simplest addition.
can be done with PS3 and select construct
#!/bin/bash
PS3='myprompt> '
select cmd in command1 command2
do
case $REPLY in
command1)
echo response1
;;
command2)
echo response2
;;
exit)
break
;;
esac
done
Or with echo and read builtins
prompt='myprompt> '
while [[ $cmd != exit ]]; do
echo -n "$prompt"
read cmd
echo ${cmd/#command/response}
done

Execute a command in a script and kill it when pressing a key

I want to write a bash script which records my voice until I press a concrete key. I have thought I could use this command
arecord -D hw -q -f cd -r 16000 speech.wav
which records from my laptop microphone and stops when the process is killed, but I don't know how to write bash code to call the process and then kill it when I press a concrete key. Can you help me?
key="q"
arecord speech.wav &
pid=$!
while read -n1 char ; do
if [ "$char" = "$key" ] ; then
kill "$pid"
break
fi
done
$! notation is the pid of last background job. the read builtin has the -n switch, with this switch only a number of characters instead of a full line is read at once.

How can I run multiple bash scripts in unison?

I'm learning Bash for a Unix class, and I'm trying to figure out how to run a script, then run a second script while the first is running and have the two interact. To clarify, the scripts look like this:
#!/bin/bash
num = 1
trap exit 0 SIGINT SIGTERM
trap "{ echo &num ; num++; }" SIGUSR1
while :
do
sleep 2
done
and the second one:
#!/bin/bash
if ps | grep "$1" > /dev/null
then
kill -SIGUSR1 $1
else
echo "Process doesn't exist"
fi
exit 0
In case the code isn't correct, the general idea is for the first script to loop until it recieves a SIGINT or SIGTERM, and echo and increment a number whenever it receives a SIGUSR1. The second script takes a pid as an argument and checks if it exists, and sends a SIGUSR1 to the given process. The problem is that when I run the first script, I can't do anything unless I move it to the background with ctrl-z, but when it's there it doesn't seem to respond to any signal except a kill signal. Any ideas on how to make this work?
You can use mycommand & to run a script in the background. Ctrl-Z stops the script, but you can then use bg to let it run in the background. In either case, you can use fg to bring it to the foreground again.
Also note that you can't have spaces around the = in assignments, and you can use let num++ to increment num. You should also singlequote the command in trap, to prevent "$num" from expanding.
All in all:
#!/bin/bash
num=1
trap exit 0 SIGINT SIGTERM
trap '{ echo $num ; let num++; }' SIGUSR1
while :
do
sleep 2
done
Finally, you can more easily check if a pid exists by just using kill -0 pid, or just attempting to sigusr1 it and check the result, to avoid grep "123" matching the substring of pid "1234" and such.
You need to make the first script run in the background. When you press Ctrl+Z it is suspended. Then you can type "bg" to make it run in the background (it will stop again if it tries to read from standard input, to allow you to switch back to it with the "fg" command).
Another way is to start script1 already in the background like this:
$ ./script1 &
The ampersand starts a job in the background and returns you to the prompt immediately.
Look in the bash man page under "JOB CONTROL" (here's a copy) for more information on how this works. The key commands to deal with jobs from an interactive shell is "jobs", "fg", and "bg".

Resources