How to exit from the current session on Linux gracefully? - linux

I need to exit from the current session and I am using the following code:
read -p "Do you want to start a new session? [Y/N] " usr_session
if [ "$usr_session" == "y" ] || [ "$usr_session" == "Y" ]; then
echo -e "`date`\t\t Exiting...\n You will need to login back...\n" >> $LOG_FILE
echo -e "Exiting...\n You will need to login back...\n"
sleep 5
curr_usr=`whoami`
pkill -9 -u $curr_usr
elif [ "$usr_session" == "n" ] || [ "usr_session" == "N" ]; then
echo -e "You are still in the same session.\n"
else
echo "Invalid input"
fi
Is there a better approach to perform the same but in more graceful manner? I feel the killing the current user process might not be safe. Any suggestions are really appreciated.
Thanks in advance.

Consider using logout.
(If you don't care to immediately terminate all running processes, including background jobs)
You could send a "softer" signal first instead of -9, to give time for the processes to shut down gracefully, then only send -9 to the processes that won't exit.
logout will work only if you are able to run this script as part of the login script (such as bashrc), not if it's run in a subshell. Alternatively (with the same effect), run the script with exec script_name from the login shell.
Another idea is to kill -HUP $PPID, assuming that the script is always run directly as a subshell of the login shell. This will signal the parent shell (the login shell) to end the session.

you could write a wrapper script and then source in , for example
file : wrap1.sh
./myscript.sh
if [ $? != 0 ];then
exit
fi
replace kill with exit 1 and from main session :
source wrap1.sh

Related

How can I pass the pid of one shell to another shell created within the parent shell and check if the parent process is running or killed?

I have a c++ file which I call using my shell script(monitoring.sh)
cfile="/home/workspace/printprocess/print"
cmp=start
if [ $1 = $cmp ]
then
$cfile
fi
This works fine when I run ./monitoring.sh start
Now I want to create another shell within this shell to store its pid and the other shell should have the pid of parent shell which is running.
It is something like this:
if [$1 = $cmp ]
then
$cfile
cat > pid_no.sh <<EOF
while (process not killed)
echo $p >pid_no.sh
I'm new to Linux.Please help with the problem.
Thanks
The ID's of shell processes (and their parents) can be derived directly using $$ or BASHPID, depending on the case, as well explained here:
https://unix.stackexchange.com/questions/62231/bashpid-and-differ-in-some-cases
There should be no need to create an additional script only to get the PID, if I understand your problem well.
Based on the input that the C program/daemon is running continuously:
cfile="/home/workspace/printprocess/print"
pid_file=/path/to/pid_file.txt
if [ "$1" = start ] ; then
# Start C program into the background
$cfile &
# Store PID in a file.
echo "$!" > $pid_file
# Wait for C program to exit (or get killed)
wait
# Remove PID file
rm $pid_file
# or use echo "something" to put something to the $pid_file.
fi

Linux exit command does nothing

I'm new to Linux and am trying to create a simple program that checks if a user exists, if exists - exits the terminal, if not - creates it. I think I've done everything except exiting the terminal.
This is my code so far:
#!/bin/bash
user_name=newUser
if [ $(getent passwd $user_name) ]
then
echo "User $user_name already exists!"
exit
else
echo "The user $user_name doesn't exist and will be added"
sudo useradd -d /home/mint/newUser $user_name
fi
edit: As I said i'm new to Linux. Can someone edit my code and post it, I need to add it to a script, maybe i can close it with while function?
The exit command simply exits the current script. If you want to exit the terminal, you need to exit (or otherwise terminate) the program which is running in that terminal.
A common way to accomplish this is to make the process which wants to exit run as a function in your interactive shell.
add_user () { /path/to/your/script "$#" || exit; }
In this case, though, I would simply advise you to keep your script as is, and leave it to the user to decide whether or not they want to close their terminal.
By the way, the construct
if [ $(command) ]
will be true if the output from command is a non-empty string. The correct way to check the exit code from command is simply
if command
possibly with output redirection if you don't want the invoking user to see any output from command.
The function above also requires your scripit to exit with an explicit error; probably change it to exit 1 to explicitly communicate an error condition back to the caller.
#!/bin/bash
# First parameter is name of user to add
user_name=$1
# Quote variable; examine exit code
if getent passwd "$user_name" >/dev/null 2>&1
then
# Notice addition of script name and redirect to stderr
echo "$0: User $user_name already exists!" >&2
# Explicitly exit with nonzero exit code
exit 1
else
# script name & redirect as above
echo "$0: The user $user_name doesn't exist and will be added" >&2
# Quote argument
sudo useradd -d /home/mint/newUser "$user_name"
fi
This has been answered on askubuntu.com. I will summarise here:
In your script, as you may have noticed, exit will only exit the current script.
A user-friendly way to achieve what you're after is to use the exit command to set a suitable return code and call your script like ./<script> && exit.
If you really want the hard way, use
kill -9 $PPID
which will attempt to kill the enclosing process.

Set timeout for shell script, to make it exit(0) when time is over

When I set up a Jenkins job and found a problem about timeout for shell script.
It works like this:
Start Jenkins → control.sh is launched → test1.sh is launched in control.sh
Part code of control.sh is like:
#!/bin/sh
source func.sh
export TIMEOUT=30
# set timeout as 30s for test1.sh
( ( sleep $TIMEOUT && function_Timeout ) & ./test1.sh )
# this line of code is in a = loop actually
# it will launch test2.sh, test3.sh... one by one
# later, I want to set 30s time out for each of them.
function_Timeout() {
if [ ! -f test1_result_file]: then
killall test1.sh
# the test1_result_file will not
# be created if test1.sh is not finished executing.
fi
}
part of func.sh is as below
#!/bin/sh
function trap_fun() {
TRAP_CODE=$?
{ if [ $TRAP_CODE -ne 0 ]; then
echo "test aborted"
else
echo "test completed"
} 2>/dev/null
trap "trap_fun" EXIT
After control.sh is launched by Jenkins job, the whole control.sh will be terminated when time is over, and the line of killall test1.sh is reached, and the Jenkins job stop and fail.
I guess it's because test1.sh is killed and exit code is not 0, so it cause this problem.
So my question is, is there someway to terminate or end the sub-script (launched by the main one, like control.sh in my case) exit with code 0?
Updated on July 1:
Thanks for the answers so far, I tried #Leon's suggestion, but I found the code 124 sent by timeout's kill action, is still caught by the trap code - trap "trap_fun" EXIT, which is in func.sh.
I added more details. I did a lot google job but still not found a proper way to resolve this problem:(
Thanks for your kind help!
Use the timeout utility from coreutils:
#!/bin/sh
timeout 30 ./test1.sh
status=$?
if [ $status -eq 124 ] #timed out
then
exit 0
fi
exit $status
Note that this is slightly different from your version of timeout handling, where all running instances of test1.sh are being terminated if any one of them times out.
I resolved this problem finally, I added the code below in each testX.sh.
trap 'exit 0' SIGTERM SIGHUP
It is to make test1.sh exit normally after it receives killall signal.
Thanks to all the help!

echo message not coming on terminal with systemd

I have systemd service, say xyzWarmup.service.
Here is the service file
[Unit]
Description=Xyz agent.
After=fooAfter.service
Before=fooBefore1.service
Before=fooBefore2.service
[Service]
# During boot the xyz.sh script reads input from /dev/console. If the user
# hits <ESC>, it will skip waiting for xyz and abc to startup.
Type=oneshot
StandardInput=tty
StandardOutput=tty
ExecStart=/usr/bin/xyz.sh start
RemainAfterExit=True
ExecStop=/usr/bin/xyz.sh stop
[Install]
WantedBy=multi-user.target
Following is the part of xyz.sh.
#! /bin/bash
#
### BEGIN INIT INFO
# Required-Stop: Post
### END INIT INFO
XYZ=/usr/bin/Xyz
prog="Xyz"
lockfile=/var/lock/subsys/$prog
msg="Completing initialization"
start() {
# Run wfw in background
ulimit -c 0
# wfw has a default timeout of 10 minutes - just pick a large value
wfw -t 3600 xyz abc >/dev/null 2>&1 &
PID=$!
# Display the message here after spawning wfw so Esc can work
echo -n $"$msg (press ESC to skip): "
while [ 1 ]; do
read -s -r -d "" -N 1 -t 0.2 CHAR || true
if [ "$CHAR" = $'\x1B' ]; then
kill -9 $PID 2>/dev/null
# fall through to wait for process to exit
fi
STATE="`ps -p $PID -o state=`"
if [ "$STATE" = "" ]; then
# has exited
wait $PID 2>/dev/null
if [ $? -eq 0 ]; then
echo "[ OK ]"
echo
exit 0
else
echo "[ FAILED ]"
echo "This is failure"
exit 1
fi
fi
done
}
When this script runs during boot I see the following message coming from the script
Completing initialization (press ESC to skip):
Updated:
This is the additional output which I see after the previous line
[[ OK ] Started Xyz agent.\n'
If you carefully see, there are 2 opening square brackets( '[' ), from this it looks like that systemd is overwriting the log messages. The first "[" comes from the initscript's "[ OK ]". Can somebody explain this better ?
I don't see "[ OK ]" or "[ FAILED ]" on my screen.
When I was using this script as initscript in Fedora14, I used to see these messages. Once, I have shifted to systemd. I have started seeing this issue.
systemd version is : systemd-201-2.fc18.9.i686 and systemd.default_standard_output=tty
Kindly help.
It looks to me that your issue here is that the script is never getting attached to the TTY. The output is showing up because you have that hard-coded to go to /dev/console in your script. With StandardInput=tty, systemd waits until the tty is available, and it's probably already in use. Your script is just sitting there not connected to input in the infinite loop. You could try StandardInput=tty-force, and I bet that will work, although I'm not sure what else that might break.
Personally, I think I might go back and rethink the approach entirely. It sounds like you want the boot to entirely block on this service, but let you skip by hitting escape. Maybe there's a better way?

How to kill a child process after a given timeout in Bash?

I have a bash script that launches a child process that crashes (actually, hangs) from time to time and with no apparent reason (closed source, so there isn't much I can do about it). As a result, I would like to be able to launch this process for a given amount of time, and kill it if it did not return successfully after a given amount of time.
Is there a simple and robust way to achieve that using bash?
P.S.: tell me if this question is better suited to serverfault or superuser.
(As seen in:
BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")
If you don't mind downloading something, use timeout (sudo apt-get install timeout) and use it like: (most Systems have it already installed otherwise use sudo apt-get install coreutils)
timeout 10 ping www.goooooogle.com
If you don't want to download something, do what timeout does internally:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )
In case that you want to do a timeout for longer bash code, use the second option as such:
( cmdpid=$BASHPID;
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &
or to get the exit codes as well:
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?
sleep 999&
t=$!
sleep 10
kill $t
I also had this question and found two more things very useful:
The SECONDS variable in bash.
The command "pgrep".
So I use something like this on the command line (OSX 10.9):
ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done
As this is a loop I included a "sleep 0.2" to keep the CPU cool. ;-)
(BTW: ping is a bad example anyway, you just would use the built-in "-t" (timeout) option.)
Assuming you have (or can easily make) a pid file for tracking the child's pid, you could then create a script that checks the modtime of the pid file and kills/respawns the process as needed. Then just put the script in crontab to run at approximately the period you need.
Let me know if you need more details. If that doesn't sound like it'd suit your needs, what about upstart?
One way is to run the program in a subshell, and communicate with the subshell through a named pipe with the read command. This way you can check the exit status of the process being run and communicate this back through the pipe.
Here's an example of timing out the yes command after 3 seconds. It gets the PID of the process using pgrep (possibly only works on Linux). There is also some problem with using a pipe in that a process opening a pipe for read will hang until it is also opened for write, and vice versa. So to prevent the read command hanging, I've "wedged" open the pipe for read with a background subshell. (Another way to prevent a freeze to open the pipe read-write, i.e. read -t 5 <>finished.pipe - however, that also may not work except with Linux.)
rm -f finished.pipe
mkfifo finished.pipe
{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!
# Get command PID
while : ; do
PID=$( pgrep -P $SUBSHELL yes )
test "$PID" = "" || break
sleep 1
done
# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &
read -t 3 FINISHED <finished.pipe
if [ "$FINISHED" = finished ] ; then
echo 'Subprocess finished'
else
echo 'Subprocess timed out'
kill $PID
fi
rm finished.pipe
Here's an attempt which tries to avoid killing a process after it has already exited, which reduces the chance of killing another process with the same process ID (although it's probably impossible to avoid this kind of error completely).
run_with_timeout ()
{
t=$1
shift
echo "running \"$*\" with timeout $t"
(
# first, run process in background
(exec sh -c "$*") &
pid=$!
echo $pid
# the timeout shell
(sleep $t ; echo timeout) &
waiter=$!
echo $waiter
# finally, allow process to end naturally
wait $pid
echo $?
) \
| (read pid
read waiter
if test $waiter != timeout ; then
read status
else
status=timeout
fi
# if we timed out, kill the process
if test $status = timeout ; then
kill $pid
exit 99
else
# if the program exited normally, kill the waiting shell
kill $waiter
exit $status
fi
)
}
Use like run_with_timeout 3 sleep 10000, which runs sleep 10000 but ends it after 3 seconds.
This is like other answers which use a background timeout process to kill the child process after a delay. I think this is almost the same as Dan's extended answer (https://stackoverflow.com/a/5161274/1351983), except the timeout shell will not be killed if it has already ended.
After this program has ended, there will still be a few lingering "sleep" processes running, but they should be harmless.
This may be a better solution than my other answer because it does not use the non-portable shell feature read -t and does not use pgrep.
Here's the third answer I've submitted here. This one handles signal interrupts and cleans up background processes when SIGINT is received. It uses the $BASHPID and exec trick used in the top answer to get the PID of a process (in this case $$ in a sh invocation). It uses a FIFO to communicate with a subshell that is responsible for killing and cleanup. (This is like the pipe in my second answer, but having a named pipe means that the signal handler can write into it too.)
run_with_timeout ()
{
t=$1 ; shift
trap cleanup 2
F=$$.fifo ; rm -f $F ; mkfifo $F
# first, run main process in background
"$#" & pid=$!
# sleeper process to time out
( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
read sleeper <$F
# control shell. read from fifo.
# final input is "finished". after that
# we clean up. we can get a timeout or a
# signal first.
( exec 0<$F
while : ; do
read input
case $input in
finished)
test $sleeper != 0 && kill $sleeper
rm -f $F
exit 0
;;
timeout)
test $pid != 0 && kill $pid
sleeper=0
;;
signal)
test $pid != 0 && kill $pid
;;
esac
done
) &
# wait for process to end
wait $pid
status=$?
echo finished >$F
return $status
}
cleanup ()
{
echo signal >$$.fifo
}
I've tried to avoid race conditions as far as I can. However, one source of error I couldn't remove is when the process ends near the same time as the timeout. For example, run_with_timeout 2 sleep 2 or run_with_timeout 0 sleep 0. For me, the latter gives an error:
timeout.sh: line 250: kill: (23248) - No such process
as it is trying to kill a process that has already exited by itself.
#Kill command after 10 seconds
timeout 10 command
#If you don't have timeout installed, this is almost the same:
sh -c '(sleep 10; kill "$$") & command'
#The same as above, with muted duplicate messages:
sh -c '(sleep 10; kill "$$" 2>/dev/null) & command'

Resources