I'm writting a bash wrapper to learn some scripting concepts. The idea is to write a script in bash and set it as a user's shell at login.
I made a while loop that reads and evals user's input, and then noticed that, whenever user typed CTRL + C, the script aborted so the user session ends.
To avoid this, I trapped SIGINT, doing nothing in the trap.
Now, the problem is that when you type CTRL + C at half of a command, it doesn't get cancelled as one would do on bash - it just ignores CTRL + C.
So, if I type ping stockoverf^Cping stackoverflow.com, I get ping stockoverfping stackoverflow.com instead of the ping stackoverflow.com that I wanted.
Is there any way to do that?
#!/bin/bash
# let's trap SIGINT (CTRL + C)
trap "" SIGINT
while true
do
read -e -p "$USER - SHIELD: `pwd`> " command
history -s $command
eval $command
done
I know this is old as all heck, but I was struggling to do something like this and came up with this solution. Hopefully it helps someone else out!
#/usr/bin/env bash
# Works ok when it is invoked as a bash script, but not when sourced!
function reset_cursor(){
echo
}
trap reset_cursor INT
while true; do
command=$( if read -e -p "> " line ; then echo "$line"; else echo "quit"; fi )
if [[ "$command" == "quit" ]] ; then
exit
else
history -s $command
eval "$command"
fi
done
trap SIGINT
By throwing the read into a subshell, you ensure that it will get killed with a sigint signal. If you trap that sigint as it percolates up to the parent, you can ignore it there and move onto the next while loop. You don't have to have reset_cursor as its own function but I find it nice in case you want to do more complicated stuff.
I had to add the if statement in the subshell because otherwise it would ignore ctrl+d - but we want it to be able 'log us out' without forcing a user to type exit or quit manually.
You could use a tool like xdotool to send Ctrl-A (begin-of-line) Ctrl-K (delete-to-end-of-line) Return (to cleanup the line)
#!/bin/bash
trap "xdotool key Ctrl+A Ctrl+k Return" SIGINT;
unset command
while [ "$command" != "quit" ] ;do
eval $command
read -e -p "$USER - SHIELD: `pwd`> " command
done
trap SIGINT
The please have a look a bash's manual page, searching for ``debug'' keyword...
man -Pless\ +/debug bash
Related
I've add a trap to my bash script so when CTRL+C is pressed a message appears Do you want to quit ? (y/n)
This works at most parts of the script, but fails at others.
I've created a simple example that shows it always failing.
#!/bin/bash
quit() {
echo "Do you want to quit ? (y/n)"
read ctrlc
if [ "$ctrlc" = 'y' ]; then
exit
fi
}
trap quit SIGINT
trap quit SIGTERM
while true; do
echo -e "\n\e[91mIs everything done ? (y/n)\e[0m"
read -i "y" -e yn
case $yn in
[Nn]* ) continue;;
[Yy]* )
echo -e "Done"
break;;
* ) echo -e "\e[91mPlease answer yes or no.\e[0m";;
esac
done
Why when I press CTRL+C does this pop up Do you want to quit ? (y/n) but not allow me to exit ? How do I solve it ?
Thanks
The above code is running without any errors in bash shell. I suspect that you have run the script in dash SHELL (some machine's default SHELL is dash).
Run your script using the below methods,
/bin/bash
or
Give executing permission to your script file (chmod 777 script.sh) and run the file like below,
./script.sh
As I commented above - inside a function, exit is treated as a synonym for return and does not terminate a program. If that is your problem, try
kill -term $$ # send this program a terminate signal
instead of just exit. It's heavy-handed but generally effective.
Note that if you also have a SIGTERM trap that will be executed.
I have written a fairly simple script here that is meant to display a text info dialog using zenity and continuously read data from a remote TCP connection and display it in the dialog. This works... However I would like for the entire script to terminate if I close the zenity dialog.
Is there a way to do this? I don't think I can check for anything in the while loop, because the script could be stalled on reading the data from the remote TCP connection.
#!/bin/bash
on_exit() {
zenity --display=:0 --error --text="Script has exited." &
}
# Options
while getopts "a:p:t:" OPTION; do case "$OPTION" in
a) address="$OPTARG";;
p) port="$OPTARG";;
t) title="$OPTARG";;
esac; done
exec &> >(zenity --display=:0 --text-info --title=$title || exit)
# doesn't make a difference? ↑
# also tried &&
trap "on_exit" EXIT
while read data < /dev/tcp/$address/$port; do
echo $data
# ...
# do some other stuff with the information
# ...
done
Note: This is going to be run on IGEL Linux. I don't have the option of installing additional packages. So, ideally the solution I'm looking for is native to Bash.
Update
I only had to make this modification to continue using exec. Or #BachLien's answer using named pipes also works.
PID=$$
exec &> >(zenity --display=:0 --text-info --title=$title; kill $PID)
I do not have zenity installed, so I tried this script to illustrate the idea.
The program terminal+cat (emmulating zenity) is executed by the function _dspMsg which runs in background (child process); cat continuously displays messages from a file ($m) which is a named pipe; the parent process is killed when terminal+cat exits.
In the mean while, another cat proccess writes messsages into pipe $m (emmulating TPC infomation feeds); it would be killed when when _dspMsg exits.
#!/bin/bash
# 1) named pipe
m=`mktemp -u /tmp/msg-XXXX=` # get a temporary filename (for named pipe)
mkfifo "$m" # create that named pipe
trap "echo END; rm $m" EXIT # remove that file when exit
# 2) zenity
_dspMsg(){ # continuously display messages
urxvt -e bash -c "cat <$m" # terminal+cat is used in place of zenity
kill $1 # kill parent pid
} # to be run in background
_dspMsg $$ & # $$ = proccess id
# 3) TCP info feeds
cat >>"$m" # feeding messages using cat
# cat is used in placed of TCP data feed
Note:
A named pipe is used as a way of communicating between parent and child processes.
To test that script, you may need to change urxvt to xterm, iTerm, or any other terminal emmulator available in your computer.
So, maybe it is what you need (untested):
#!/bin/bash
while getopts "a:p:t:" OPTION; do case "$OPTION" in
a) address="$OPTARG";;
p) port="$OPTARG";;
t) title="$OPTARG";;
esac; done
m=`mktemp -u /tmp/msg-XXXX=`
mkfifo "$m"
trap "zenity --display=:0 --error --text='Script has exited.' & rm $m" EXIT
_dspMsg(){
zenity --display=:0 --text-info --title="$title" <"$m"
kill $1
}
_dspMsg $$ &
while read data < /dev/tcp/$address/$port; do
echo $data >>"$m"
done
You can pipe all of the output of the while loop into zenity, getting rid of the need for exec &>.
while read data < "/dev/tcp/$address/$port"; do
echo "$data"
done | zenity --display=:0 --text-info --title="$title"
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
I have a shellscript as follows. This doesn't terminates on pressing Ctrl-C. Can you guide me on how to modify the following code to kill the execution on Ctrl-C as input.
#!/bin/bash
validateURL()
{
regex='(https?|ftp|file)://[-A-Za-z0-9\+&##/%?=~_|!:,.;]*[-A-Za-z0-9\+&##/%=~_|]'
string=$1
if [[ $string =~ $regex ]]
then
echo "0"
else
echo "1"
fi
}
RED='\033[0;31m'
echo -n "Enter the URL :"
while read URL_REGISTRY
do
if [ $(validateURL $URL_REGISTRY) == "0" ]
then
break
else
echo -e "${RED}Wrong URL entered."
tput sgr0
echo -n "Enter the URL again :"
fi
done
The only way this can happen is if your shell blocks SIGINT. As per your description, your shell seems to do it. Reset the SIGINT in your shell so that your script receives SIGINT.
Run the following in your shell and run the script:
trap - SIGINT
Try:
stty intr "^C"
If that does not work, try:
stty -a
and figure out what is wrong with your settings. If you can't, update your answer with the output of stty -a.
Also make sure that you have not trapped the interrupt signal (2) as mentioned in the other answers.
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".