Trap doesn't exit the loop - bash

I'm trying to do a cleanup using trap command. The safe_cancel function is being called when I hit a Ctrl + C but the script isn't being exited. I have to use Ctrl + Z to suspend the script and then kill.
The foo is another script I have in my PATH which returns an exit 1 if it receives an invalid argument.
What am I lacking or doing wrong in this script?
#!/bin/bash
safe_cancel () {
echo "Cancelling..."
# do some cleanup here
exit 1
}
trap safe_cancel 1
while true; do
read -p "Choose an option: " someOption < /dev/tty
foo $someOption
if [[ $? == 0 ]]
then
break
exit 0
fi
done
Additional details:
I'm writing this script for a Git hook. Apparently, git hooks aren't exactly expecting a standard in/out so I have to explicitly use /dev/tty
Edit:
When using this as part of a git hook, I'm receiving the error
read: read error: 0: Input/output error
and it's an infinite loop

Signal 1 is SIGHUP, which is raised if the terminal goes away, for example because you were connected from a remote machine and your session was interrupted because the network disconnected. When you press Ctrl+C, this sends SIGINT.
trap safe_cancel HUP INT
This may or may not be related to the error you get with Git.

Related

Runit exits with error if process tells itself to go down

I'm seeing some unexpected behavior with runit and not sure how to get it to do what I want without throwing an error during termination. I have a process that sometimes knows it should stop itself and not let itself be restarted (thus should call sv d on itself). This works if I never change the user but produces errors if I switch to a non-root user when running.
I'll use the same finish script for both examples:
#!/bin/bash -e
echo "downtest finished with exit code $1 and exit status $2"
The run script that works as expected (prints downtest finished with exit code 0 and exit status 0 to syslog):
#!/bin/bash -e
exec 2>&1
echo "running downtest"
sv d downtest
exit 0
The run script that doesn't work as expected (prints downtest finished with exit code -1 and exit status 15 to syslog):
#!/bin/bash -e
exec 2>&1
echo "running downtest"
chpst -u ubuntu sudo sv d downtest
exit 0
I get the same result if I use su ubuntu instead of chpst.
Any ideas on why I see this behavior and how to fix it so calling sudo sv d downtest results in a clean process exit rather than returning error status codes?
sv d sends a SIGTERM if the process is still running. This is signal 15, hence the error being handled in the manner in question.
By contrast, to tell a running program not to start up again after it exits on its own (thus allowing that opportunity), use sv o (once) instead.
Alternately, you can trap SIGTERM in your script when you're expecting it:
trap 'exit 0' TERM
If you want to make this conditional:
trap 'if [[ $ignore_sigterm ]]; then exit 0; fi' TERM
...and then run
ignore_sigterm=1
before triggering sv d.
Has a workaround try a subshell for running (chpst -u ubuntu sudo sv d downtest) that will help to allow calling the last exit 0 since now is not being called because is exiting before.
#!/bin/sh
exec 2>&1
echo "running downtest"
(sudo sv d downtest)
exit 0
Indeed, for stopping the process you don’t need chpst -u ubuntu if want to stop or control the service as another user just need to adjust the permissions to the ./supervise directory that’s why probably you are getting the exit code -1
Checking the runsv man:
Two arguments are given to ./finish. The first one is ./run’s exit code, or -1 if ./run didn’t exit normally. The second one is the least significant byte of the exit status as determined by waitpid(2); for instance it is 0 if ./run exited normally, and the signal number if ./run was terminated by a signal. If runsv cannot start ./run for some reason, the exit code is 111 and the status is 0.
And from the faq:
Is it possible to allow a user other than root to control a service
Using the sv program to control a service, or query its status informations, only works as root. Is it possible to allow non-root users to control a service too?
Answer: Yes, you simply need to adjust file system permissions for the ./supervise/ subdirectory in the service directory. E.g.: to allow the user burdon to control the service dhcp, change to the dhcp service directory, and do
# chmod 755 ./supervise
# chown burdon ./supervise/ok ./supervise/control ./supervise/status
In case you would like to full stop/start you could remove the symlink of your run service, but that will imply to create it again when you want the service up.
Just in case, because of this and other cases, I came up with immortal to simplify the stop/start/restart/retries of services without root privileges, full based on daemontools & runit just adapted to some new flows.

Implement shortcut keys in a script

I have a program, and while it is running I'd like to give the user the ability to perform an action e.g. exit the program by pressing ctrl+x .
It would be great if anyone could help, can't seem to find the correct syntax online.
echo "Many thanks"
You can trap the keyboard interrupt SIGINT generated by Ctrl+C with something like this:
#!/bin/bash
tidy_close() {
echo "Ending gracefully, stage $1"
exit 0
}
trap 'tidy_close $stage' INT
while :
do
(( stage++ ))
echo "Pondering the answers"
sleep 2
done
The function can have any code you need to shutdown. The stage variable is just to illustrate how to pass a value, if you need it.

Trap ERR only works once?

I'm writing a script that waits until a bunch of directories exist before starting a service. It basically consists of an infinite loop that breaks at the end, or continues if any of the needed directories aren't found. Simplified, the algorithm itself looks like
loop_while_false() {
trap continue ERR
while true; do
printf .
sleep 1
false
break
done
trap ERR
echo
}
(I'm aware I could accomplish this particular behavior with until or while !, but that's tangential to the question.)
The first time I run this, I get the expected output of a long series of dots until I hit ^c. But if I run it again, I just get one dot. If I don't hit ^c, but redefine the loop to be finite, then, in a new shell, the trap works multiple times. But why is ^c breaking the trap for the life of the shell? Even weirder (I spent extra time on this while StackExchange was upgrading hardware) if you write the function this way, it doesn't break:
loop_while_noread() {
trap continue ERR
while true; do
printf .
read -t1 -n1
break
done
trap ERR
echo
}
Unless you run loop_while_false first, and kill it with ^c. Here's an example session:
$ trap -p
trap -- 'shell_session_update' EXIT
$ loop_while_noread
...q
$ loop_while_noread
...r
$ loop_while_noread
....^C
$ loop_while_noread
..q
$ trap -p
trap -- 'shell_session_update' EXIT
trap -- 'continue' ERR
$ loop_while_false
.....^C
$ trap -p
trap -- 'shell_session_update' EXIT
trap -- 'continue' ERR
$ loop_while_false
.
$ loop_while_noread
.
It as if there's a weird relationship between sleep or false and trap. Is this expected behavior?
I'm using bash 3.2.57(1)-release on OS X El Capitan.
It's certainly a bug. You can work around it by changing the sleep command to:
sleep 1||:
I can't find any bug reports, but I did a little poking against 4.3.30(1) with gdb, and established that after the sleep 1 returns with an error (because it was interrupted), something fails in the execution of the trap ERR command, with the result that the SIG_INPROGRESS flag is never reset for ERR. That flag suppresses future execution of trap ERR, even though it is still enabled.
I didn't get into the part where "something fails in the execution"; when gdb steps over parse_and_execute (trap_command, tag, flags);, the function never returns and I end up back at the bash prompt, so I suppose that a longjmp happens at some point. (The SIG_INPROGRESS flag would be reset after parse_and_execute returns, so the fact that the function doesn't return explains why the flag is not reset.)
All this action is in trap.c inside _run_trap_internal.

Hudson : "yes: standard output: Broken pipe"

I need to run a shell script in hudson. That script needs an answer from the user. To give an automatic answer I did the following command line :
yes | ./MyScript.sh
This works well in Ubuntu terminal. But when I use the same command in the Hudson job, the script will be automated and do all the needed work, but at the end, I get these two lines of error :
yes: standard output: Broken pipe
yes: write error
And this causes the failure to my Hudson job.
How should I change my command line to work well in Hudson?
But how would you explain that I dont get this error while running the script locally, but I get the error when running it remotely from a Hudson job?
When you are running it in a terminal (locally); yes is killed by SIGPIPE signal that is generated when it tries to write to the pipe when MyScript.sh has already exited.
Whatever runs the command (remotely) in Hudson traps that signal (set its handler to SIG_IGN, you can test it by running trap command and searching for SIGPIPE in the output) and it doesn't restore the signal for new child processes (yes and whatever runs MyScript.sh e.g., sh in your case). It leads to the write error (EPIPE) instead of the signal. yes detects the write error and reports it.
You can simply ignore the error message:
yes 2>/dev/null | ./MyScript.sh
You could also report the bug against the component that runs the pipeline. The bug is in not restoring SIGPIPE to the default handler after the child is forked. It is what programs expect when they are run in a terminal on POSIX systems. Though I don't know whether there is a standard way to do it for a java-based program. jvm probably raises an exception for every write error so not-dying on SIGPIPE is not a problem for a java program.
It is common for daemons such as hudson process to ignore SIGPIPE signal. You don't want your daemon to die only because the process you are communicating with dies and you would check for write errors anyway.
Ordinary programs that are written to be run in a terminal do not check status of every printf() for errors but you want them to die if programs down the pipeline die e.g., if you run source | sink pipeline; usually you want source process to exit as soon as possible if sink exits.
EPIPE write error is returned if SIGPIPE signal is disabled (as it looks like in hudson's case) or if a program does not die on receiving it (yes program does not defined any handlers for SIGPIPE so it should die on receiving the signal).
I don't want to ignore the error, I want to do the right command or fix to get rid of the error.
the only way yes process stops if it is killed or encountered a write error. If SIGPIPE signal is set to be ignored (by the parent) and no other signal kills the process then yes receives write error on ./MyScript.sh exit. There are no other options if you use yes program.
SIGPIPE signal and EPIPE error communicate the exact same information -- pipe is broken. If SIGPIPE were enabled for yes process then you wouldn't see the error. And only because you see it; nothing new happens. It just means that ./MyScript.sh exited (successfully or unsuccessfully -- doesn't matter).
I had this error, and my problem with it is not that it output yes: standard output: Broken pipe but rather than it returns an error code.
Because I run my script with bash strict mode including -o pipefail, when yes "errors" it causes my script to error.
How to avoid an error
The way I avoided this is like so:
bash -c "yes || true" | my-script.sh
You are trying to use the yes program to pipe to the script? or echo yes to the script? If the process is working through jenkins, add "; true" to the end of your shell command.
Since yes and ./MyScript.sh can each be run in an explicit subshell, it is possible to background the yes command, send yespid to the ./MyScript.sh subshell and then implement a trap on EXIT there to manually terminate the yes command. (The trap on EXIT should always be implemented in the subshell of the last command of a piped commmand sequence).
# avoid hangup or "broken pipe" error message when parent process set SIGPIPE to be ignored
# sleep 0 or cat /dev/null: do nothing but with external command (for a shell builtin command see: help :)
(
trap "" PIPE
( (sleep 0; exec yes) & echo ${!}; wait ${!} ) |
(
trap 'trap - EXIT; kill "$yespid"; exit 0' EXIT
yespid="$(head -n 1)"
head -n 10 # replacement for ./MyScript.sh
)
echo ${PIPESTATUS[*]}
)
If you want to exit the yes subshell with exit code 0 you can do this as well:
# avoid hangup or "broken pipe" error message when parent process set SIGPIPE to be ignored
# set exit code of yes subshell to 0
(
trap "" PIPE
(
trap 'trap - TERM; echo "kill from yes subshell ..." 1>&2; kill "${!}"; exit 0' TERM
subshell_pid="$(bash -c 'echo "$PPID"')"
(sleep 0; exec yes) & echo "${subshell_pid}"; wait ${!}
) |
(
trap 'trap - EXIT; kill -s TERM "$subshell_pid"; exit' EXIT
subshell_pid="$(head -n 1)"
head -n 10 # replacement for ./MyScript.sh
)
echo ${PIPESTATUS[*]}
)
The command yes being running in an infinite loop I supposed that this might be the solution :
yes | head -1 | ./MyScript.sh #only one "Y" would be output of the command yes
But, I got the same error.
We can redirect the error to /dev/null as suggested by #J.F. Sebastian, or enforce that the command is correct by this :
yes | head -1 | ./MyScript.sh || yes
But, this suggestions were less appreciated. And so, I had to create my own named pipe, as follow :
mkfifo /tmp/my_fifo #to create the named pipe
exec 3<>/tmp/my_fifo #to make the named pipe in read and write mode by assigning it to a file descriptor
echo "Y" >/tmp/my_fifo #to write into the named pipe, "Y" is the default value of yes
./MyScript.sh </tmp/my_fifo #to read from the named pipe
rm /tmp/my_fifo #remove the named pipe
I'm expecting more valuable solutions with greater explainations.
Here it is an explaination for a file descriptor in linux.
Thanks

Terminating a shell function non-interactively

Is there a way to terminate a shell function non-interactively without killing the shell that's running it?
I know that the shell can be told how to respond to a signal (e.g. USR1), but I can't figure out how the signal handler would terminate the function.
If necessary you may assume that the function to be terminate has been written in such a way that it is "terminable" (i.e. by declaring some suitable options).
(My immediate interest is in how to do this for zsh, but I'm also interested in knowing how to do it for bash and for /bin/sh.)
EDIT: In response to Rob Watt's suggestion:
% donothing () { echo $$; sleep 1000000 }
% donothing
47139
If at this point I hit Ctrl-C at the same terminal that is running the shell, then the function donothing does indeed terminate, and I get the command prompt back. But if instead, from a different shell session, I run
% kill -s INT 47139
...the donothing function does not terminate.
Maybe I'm not fully understand what you want, but maybe something like this?
trap "stopme=1" 2
function longcycle() {
last=$1
for i in 1 2 3 4 5
do
[ ! -z "$stopme" ] && return
echo $i
sleep 1
done
}
stopme=""
echo "Start 1st cycle"
longcycle
echo "1st cycle end"
echo "2nd cycle"
stopme=""
longcycle
echo "2nd cycle end"
The above is for bash. Run it, and try press CTRL-C.
Or for not interactively, Save the above as for example my_command, then try:
$ ./my_command & #into background
$ kill -2 $! #send CTRL-C to the bg process
EDIT:
Solution for your sleep example in the bash:
$ donothing() { trap '[[ $mypid ]] && trap - 2 && kill $mypid' 0 2; sleep 1000000 & mypid=$!;wait; }
$ donothing
when you send a signal from another terminal will terminate it. Remeber, signal '0' je "normal end of the process". Semantic name: 0=EXIT, 2=INT... etc.
and remeber too, than signals are sending to processes not to the functions. In your example, the process is the current (interactive shell), so must use the wait trick to get something interrupt-able... Not a nice solution - but the only way when want interrupt something what is running in interactive shell (not a forked one) from the another terminal...

Resources