How to evaluate results of pipes in a bash script - bash

I need help for the following problem:
I'd like to kill all instances of a program, let's say xpdf.
At the prompt the following works as intended:
$ ps -e | grep xpdf | sed -n -e "s/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p" | xargs kill -SIGTERM
(the sed-step is required to extract the PID).
However, there might be the case that no xpdf-process is running. Then it would be difficult, to embed the line into a script, because it aborts after it immediately with a message from kill. What can I do about it?
I tried (in a script)
#!/bin/bash
#
set -x
test=""
echo "test = < $test >"
test=`ps -e | grep xpdf | sed -n -e "s/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p"`
echo "test = < $test >"
if [ -z "$test" ]; then echo "xpdf läuft nicht";
else echo "$test" | xargs -d" " kill -SIGTERM
fi
When running the script above I get
$ Kill_ps
+ test=
+ echo 'test = < >'
test = < >
++ ps -e
++ grep xpdf
++ sed -n -e 's/^[^0-9]*\([0-9]*\)[^0-9]\{1,\}.*$/\1/p'
+ test='21538
24654
24804
24805'
+ echo 'test = < 21538
24654
24804
24805 >'
test = < 21538
24654
24804
24805 >
+ '[' -z '21538
24654
24804
24805' ']'
+ xargs '-d ' kill -SIGTERM
+ echo '21538
24654
24804
24805'
kill: failed to parse argument: '21538
24654
24804
24805
Some unexpected happens: In test there are more PIDs then processes
At the prompt:
$ ps -e | grep xpd
21538 pts/3 00:00:00 xpdf.real
24654 pts/2 00:00:00 xpdf.real
When running the script again, the 24* PIDs change.
So here are my questions:
Where do the additional PIDs come from?
What can I do to handle the situation, in which no process I want to kill is running (or why does xargs not accept echo "$test" as input)? (I want my script not to be aborted)
Thanks in advance.

You can use pkill(1) or killall(1) instead of parsing ps output. (Parsing human-readable output for scripting is not recommended. This is an example.)
Usage:
pkill xpdf
killall xpdf # Note that on solaris, it kills all the processes that you can kill. https://unix.stackexchange.com/q/252349#comment435182_252356
pgrep xpdf # Lists pids
ps -C xpdf -o pid= # Lists pids
Note that tools like pkill, killall, pgrep will perform similar to ps -e | grep. So, if you do a pkill sh, it will try to kill sh, bash, ssh etc, since they all match the pattern.
But to answer your question about skipping xargs from running when there is nothing on the input, you can use -r option for (GNU) xargs.
-r, --no-run-if-empty
If the standard input does not contain any nonblanks, do not run the command.
Normally, the command is run once even if there is no input.
This option is a GNU extension.

This answer is for the case where killall or pkill (suggested in this answer) are not enough for you. For example if you really want to print "xpdf läuft nicht" if there is no pid to kill or applying kill -SIGTERM because you want to be sure of the signal you send to your pids or whatever.
You could use a bash loop instead of xargs and sed. It's pretty simple to iterate over CSV/column outputs:
count=0
while read -r uid pid ppid trash; do
kill -SIGTERM $pid
(( count++ ))
done < <(ps -ef|grep xpdf)
[[ $count -le 0 ]] && echo "xpdf läuft nicht"
There is a quicker way using pgrep (the previous one was for illustrate how to iterate over column-based outputs of commands with bash):
count=0
while read -r; do
kill -SIGTERM $REPLY
(( count++ ))
done < <(pgrep xpdf)
[[ $count -le 0 ]] && echo "xpdf läuft nicht"
If you're version of xargs can provide you the --no-run-if-empty you can still use it with pgrep (like suggested in this answer), but it's not available on the BSD or POSIX version. It is my case on macOS for example...
awk could also do the trick (with only one command after the pipe):
ps -ef|awk 'BEGIN {c="0"} {if($0 ~ "xpdf" && !($0 ~ "awk")){c++; system("kill -SIGTERM "$2)}} END {if(c <= 0){print "xpdf läuft nicht"}}'

Related

Checking if the same user has 2 terminals open

I'm trying to detect if 1 user has 2 terminal sessions open.
How can this be achieved?
What I tried is the following:
linuxName="$(id -u -n)"
for user in "$#"; do
if echo "$who" | grep -q "$linuxName"; then
echo "[WARNING] You're already logged in";
closeSessionIfNotRoot
fi;
done;
sort count and filter these that have more then 2.
id -u -n | sort | uniq -c | awk '$1 > 2'
#!/usr/bin/bash
linuxName="$(id -un)"
for user in "$#"; do
process_counter=$(pgrep -u "$user" bash 2>&1 | wc -l)
if [[ "$user" == $(who | grep -o "$linuxName") ]]; then
echo "[WARNING] You're already logged in $user"
if [[ "$process_counter" -gt 1 ]]; then
((process_counter--))
pgrep -u "$user" bash | tail -"$process_counter" |
xargs -I % kill -9 %
echo "closed other terminals: $user"
fi
fi
done
script +arguments[in this case, users_names], example, ./script alex zara sophia
Explain answer
Count terminals open by user and save to variable process_counter
process_counter=$(pgrep -u "$user" bash 2>&1 | wc -l)
2>1 REDIRECTS output error to NO SHOW. this in case you wrote a user_name that doesn't exist in the system.
pgrep -u $user bash | wc -l returns number of terminals open. u $user is for returning the process from that user, so this uniq.
pgrep shows the process you are searching for,e.g., pgreg bash returns a lists of process IDs
returns:
363570
369836
370113
this checks if the $user --> ./file alex zara is the same as in terminal $linuxName
[[ "$user" == $(who | grep -o "$linuxName") ]]
grep -o only returns the username alex instead of alex :1 2021-07....
[[ ]] is to make comparisons in bash
xargs is for pasing previous argument to a loop
tail -"$process_counter" | xargs -I % kill -9 %
tail -$process_counter is for showing the last terminals open
363570
369836 #return only this
370113 #return only this
remember that we REST minus 1 to process_counter because we want to leave 1 terminal open, this is done with ((process_counter--)).
So 3 terminals open - 1 terminal = 2 terminals to close.
now we loop with XARGS
363570
369836 #CLOSE THIS kill -9 369836
370113 #CLOSE THIS kill -9 370113
-I arguments tells xargs to assign a variable, represented with symbol %. but this symbol can be anything,like #,$, and then what we want to do is kill each process, so that's why kill -9 %
Check limits.conf which is configuration file for the pam_limits module.
In that file, you can define maxlogins as the maximum number of logins for a user (this limit does not apply to user with uid=0)
See https://www.man7.org/linux/man-pages/man5/limits.conf.5.html.

Matching one line from continuous stream in Linux shell

How can I make the following commands exit immediately after the first line is matched? I understand that SIGPIPE is not sent to cat until it tries to write next time (tail bug report), but I don't understand how to solve this issue.
cat <( echo -ne "asdf1\nzxcv1\nasdf2\n"; sleep 5; echo -ne "zxcv2\nasdf3\n" ) | grep --line-buffered zxcv | head --lines=1
cat <( echo -ne "asdf1\nzxcv1\nasdf2\n"; sleep 5; echo -ne "zxcv2\nasdf3\n" ) | grep --max-count=1 zxcv
NB: I actually had tail --follow before the pipesign, but replaced it with catand sleep to simplify testing. The shell in question is GNU bash 4.4.12(1)-release, and I'm running MINGW that came with Git-for-Windows 2.12.2.2.
CLARIFICATION: I have a jboss server which is started in a docker container and which outputs couple thousand lines of text within three minutes to a log file. My goal is to watch this file until a status line is printed, analyze line contents and return it to a human or Jenkins user. Of course, I can grep whole file and sleep for a second in a loop, but I'd rather avoid this if at all possible. Furthermore, this looping would interfere with my usage of timeout routine to limit maximum execution time. So, is it possible to listen for a pipe until a certain line appears and stop as soon as that happens?
Related question: Why does bash ignore SIGINT if its currently running child handles it?
Interesting question! I've verified that head dies after printing the first line (removed background job noise):
$ (printf '%s\n' a b a; sleep 5; printf '%s\n' a) | grep --line-buffered a | head --lines=1 & sleep 1; pstree $$
a
bash─┬─bash───sleep
├─grep
└─pstree
At first glance, it appears head doesn't send SIGPIPE, but I get conflicting information from running strace grep:
$ (printf '%s\n' a b a; sleep 10; printf '%s\n' a) | strace grep --line-buffered a | head --lines=1
…
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=21950, si_uid=1000} ---
+++ killed by SIGPIPE +++
… and killing grep:
$ (printf '%s\n' a b a; sleep 10; printf '%s\n' a) | grep --line-buffered a | head --lines=1 & sleep 1; kill -PIPE $(pgrep grep); sleep 5; pstree $$
a
bash─┬─bash───sleep
└─pstree
Killing grep and then sleep fixes the issue:
$ (printf '%s\n' a b a; sleep 10; printf '%s\n' a) | grep --line-buffered a | head --lines=1 & sleep 1; kill -PIPE $(pgrep grep); sleep 1; kill -PIPE $(pgrep sleep); sleep 5; pstree $$
a
bash───pstree
Conclusion: WTF?
I've ended up doing following to be able to break following log both on a matching line and after a timeout.
#!/bin/sh
TOP_PID=$$
container_id="$1"
LOG_PATH=/opt/jboss/jboss-eap-6.2/standalone/log/server.log
await_startup () {
status=$(check_status)
follow_log --timeout $timeout &
local bgjob_pid; local bgjob_status;
bgjob_pid=$(jobs -p)
test -n "$bgjob_pid" || die "Could not start background job to follow log."
bgjob_status=true
while [ "$status" = "not started" ] && $bgjob_status; do
sleep 1s
status=$(check_status)
if kill -0 $bgjob_pid 2>/dev/null; then
bgjob_status=true
else
bgjob_status=false
fi
done
kill -KILL $bgjob_pid 2>/dev/null
}
follow_log () {
# argument parsing skipped...
docker exec $container_id timeout $timeout tail --follow=name ---disable-inotify --max-unchanged-stats=2 /$LOG_PATH
}
check_status () {
local line;
line=$(docker exec $container_id grep --extended-regexp --only-matching 'JBoss EAP .+ started.+in' /$LOG_PATH | tail --lines=1)
if [ -z "$line" ]; then
printf "not started"
elif printf "%s" "$line" | grep --quiet "with errors"; then
printf "started and unhealthy"
else
printf "healthy"
fi
}
die () {
test -n "$1" && printf "%s\n" "$1"
kill -s TERM $TOP_PID
return 1
} 1>&2

How to modify script to exclude some processes?

Welcome, I have a short script to kill processes which works longer than specified time for UIDs bigger than. How to exclude for example mc command from killing?
#!/bin/bash
#
#Put the minimum(!) UID to kill processes
UID_KILL=500
#Put the time in seconds which the process is allowed to run below
KILL_TIME=300
KILL_LIST=`{
ps -eo uid,pid,lstart | tail -n+2 |
while read PROC_UID PROC_PID PROC_LSTART; do
SECONDS=$[$(date +%s) - $(date -d"$PROC_LSTART" +%s)]
if [ $PROC_UID -ge $UID_KILL -a $SECONDS -gt $KILL_TIME ]; then
echo -n "$PROC_PID "
fi
done
}`
#KILLING LOOP
while sleep 1
do
if [[ -n $KILL_LIST ]]
then
kill $KILL_LIST
fi
done
change inner command like this :
ps -eo comm,uid,pid,lstart | tail -n+2 | grep -v '^your_command' | ...
this will exclude 'your_command' from the list.
see STANDARD FORMAT SPECIFIERS in man ps for more about ps -o.

Bash - output of command seems to be an integer but "[" complains

I am checking to see if a process on a remote server has been killed. The code I'm using is:
if [ `ssh -t -t -i id_dsa headless#remoteserver.com "ps -auxwww |grep pipeline| wc -l" | sed -e 's/^[ \t]*//'` -lt 3 ]
then
echo "PIPELINE STOPPED SUCCESSFULLY"
exit 0
else
echo "PIPELINE WAS NOT STOPPED SUCCESSFULLY"
exit 1
fi
However when I execute this I get:
: integer expression expected
PIPELINE WAS NOT STOPPED SUCCESSFULLY
1
The actual value returned is "1" with no whitespace. I checked that by:
vim <(ssh -t -t -i id_dsa headless#remoteserver.com "ps -auxwww |grep pipeline| wc -l" | sed -e 's/^[ \t]*//')
and then ":set list" which showed only the integer and a line feed as the returned value.
I'm at a loss here as to why this is not working.
If the output of the ssh command is truly just an integer preceded by optional tabs, then you shouldn't need the sed command; the shell will strip the leading and/or trailing whitespace as unnecessary before using it as an operand for the -lt operator.
if [ $(ssh -tti id_dsa headless#remoteserver.com "ps -auxwww | grep -c pipeline") -lt 3 ]; then
It is possible that result of the ssh is not the same when you run it manually as when it runs in the shell. You might try saving it in a variable so you can output it before testing it in your script:
result=$( ssh -tti id_dsa headless#remoteserver.com "ps -auxwww | grep -c pipeline" )
if [ $result -lt 3 ];
The return value you get is not entirely a digit. Maybe some shell-metacharacter/linefeed/whatever gets into your way here:
#!/bin/bash
var=$(ssh -t -t -i id_dsa headless#remoteserver.com "ps auxwww |grep -c pipeline")
echo $var
# just to prove my point here
# Remove all digits, and look wether there is a rest -> then its not integer
test -z "$var" -o -n "`echo $var | tr -d '[0-9]'`" && echo not-integer
# get out all the digits to use them for the arithmetic comparison
var2=$(grep -o "[0-9]" <<<"$var")
echo $var2
if [[ $var2 -lt 3 ]]
then
echo "PIPELINE STOPPED SUCCESSFULLY"
exit 0
else
echo "PIPELINE WAS NOT STOPPED SUCCESSFULLY"
exit 1
fi
As user mbratch noticed I was getting a "\r" in the returned value in addition to the expected "\n". So I changed my sed script so that it stripped out the "\r" instead of the whitespace (which chepner pointed out was unnecessary).
sed -e 's/\r*$//'

restricting xargs from reading stdin to buffer

It looks like xargs reads input lines from stdin, even when it is already running maximum number of process that it can run.
Here is an example:
#!/bin/bash
function xTrigger()
{
for ii in `seq 1 100`; do echo $ii; sleep 2; done
}
function xRunner()
{
sleep 10;
echo $1;
}
export -f xTrigger
export -f xRunner
bash -c "xTrigger" | xargs -n 1 -P 1 -i bash -c "xRunner {}"
20 seconds after starting above process, I killall xTrigger, so but xargs has buffered everything xTrigger printed, so xRunner continued to print 1..10. What I want is for it to print only 1,2
Is there anyway by which we can change this behaviour and get xargs to read from stdin only when it wants to start a new command, so that xTrigger would wait at the echo statement until xargs reads from it? My stdin has very dynamic content so this would be very useful.
Trying to stick to xargs just because it would be stable and elegent. Want to write extra code only if there is no easy way of doing it with xargs.
Thanks for all your help!
Don't you have to kill the Bash PID of xTrigger()?
bash -c "echo $$; xTrigger" | xargs -n 1 -P 1 bash -c 'xRunner "$#"' _
kill -HUP <PID>
On my system, xargs will by default halt if one of the jobs it is running exits with a non-zero return code. Therefore, you should be sending the signal to the bash pid that is running XRunner.
Got xTrigger to to generate next trigger only when there are no 'bash -c xRunner' jobs running. Works great now:
#!/bin/bash
function xTrigger()
{
for ii in `seq 1 100`; do
echo $ii;
while [[ $(psgrep xRunner|grep -v xargs|wc -l) -ge 1 ]]; do
sleep 2;
done
done
}
function xRunner()
{
sleep 10;
echo $1;
}
export -f xTrigger
export -f xRunner
bash -c "xTrigger" | xargs -n 1 -P 1 -i bash -c "xRunner {}"

Resources