bash script - how to get running process lapsed time and argument - bash

assuming im running python scripts with arguments like the following:
501 8694 8590 0 11:01PM ttys011 0:02.03 /Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python test.py 100 yes
501 8699 8696 0 11:01PM ttys012 0:01.03 /Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python test.py 100 no
and now i want to use bash script to extract those two processes that are currently running, and get the amount of time each process has been running and argument yes/no. this is what i have done so far to just extract the time taken for each process:
#!/bin/bash
commands=$(ps -ef | grep -v grep | grep "while-file")
var=0
for command in $commands;do
((var++))
if [ $var = 7 ];then
echo "time taken:$command"
fi
if [ $var = 17 ];then
echo "time taken:$command"
fi
done
but this is not clean. if i happen to have 3 processes running, my code will break
So my questions are:
how can i get the amount of time each process has been running. in this case the 1st process has been running for 2mins3sec while the 2nd one is 1min3sec.
how can i get the argument yes or no from each line.
--Edit--
ok so based on twalberg comment i did this:
$ps -ef | grep [w]hile-file.py | awk '{print $7, $NF}'
0:00.00 while-file.py
0:00.02 yes
0:00.03 no
how do i ignore the first line: 0:00.00 while-file.py

Related

Exit tail upon string detection

I'm writing a barrier to stall the execution of a script until a certain keyword is logged. The script is pretty simple:
tail -F -n0 logfile.log | while read LINE; do
[[ "$LINE" == *'STOP'* ]] && echo ${LINE} && break;
done
or
tail -F -n0 logfile.log | grep -m1 STOP
The thing is it doesn't quit as soon as the keyword is detected, but only after the next line is written. I.e:
printf "foo\n" >> logfile.log # keeps reading
printf "foo\n" >> logfile.log # keeps reading
printf "STOP\n" >> logfile.log # STOP printed
printf "foo\n" >> logfile.log # code exits at last
Unfortunately I can't rely on the fact that another line will be logged after the "STOP" (not within an interval useful for my purposes at least).
The workaround found so far is to tail also another file I know for sure gets updated quite frequently, but what is the "clean" solution so that the code will exit right after it logs STOP?
In bash, when executing a command of the form
command1 | command2
and command2 dies or terminates, the pipe which receives /dev/stdout from command1 becomes broken. This, however, does not terminate command1 instantly.
So to achieve what you want is to use process substitution and not a pipe
awk '/STOP/{exit}1' < <(tail -f logfile)
When you use awk, you can see the behaviour in a bit more detail:
$ touch logfile
$ tail -f logfile | awk '/STOP/{exit}1;END{print "end"}'
This awk program will check if "STOP" is seen, and if not print the line again. If "STOP" is seen it will print "end"
When you do in another terminal
$ echo "a" >> logfile
$ echo "STOP" >> logfile
$ echo "b" >> logfile
You see that awk prints the following output:
a # result of print
end # awk test STOP, exits and executes END statement
Furthermore, if you look more closely, you see that awk is at this point already terminated.
ps before sending "STOP":
13625 pts/39 SN 0:00 | \_ bash
32151 pts/39 SN+ 0:00 | \_ tail -f foo
32152 pts/39 SN+ 0:00 | \_ awk 1;/STOP/{exit}1;END{print "end"}
ps after sending "STOP":
13625 pts/39 SN 0:00 | \_ bash
32151 pts/39 SN+ 0:00 | \_ tail -f foo
So the awk program terminated, but tail did not crash because it is not yet aware the pipe is broken as it did not attempt to write to it.
When you do the following in the terminal with the pipeline, you see the exit status of tail:
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
$ 141 0
Which states that awk terminated nicely, but tail terminated with exit code 141 which means SIGPIPE.

bash: differing newline count when assigning result to variable [duplicate]

This question already has answers here:
Check number of running scripts using ps
(4 answers)
Closed 6 years ago.
let's say I want to see how many copies of a program are already running. I could do something like this:
ps ax | grep -c "$0"
that command by itself produces the expected result. BUT if I attempt to assign the output to a variable, it gets incremented by one! No matter how I try it:
var=$(ps ax | grep "$0" | sed -n '$=')
var=`ps ax | grep -c "$0"`
can someone please show me the right way to capture the correct output?
it would also be great to know why this is happening..
UPDATE
after the first response from #fedorqui I realize I wasn't clear enough. let me elaborate:
I am running all three commands above in the same bash script. When I run the first one, it prints out the number 2: the program itself and the grep process with that program as an argument. when I run those same commands within variable assignments, the number 3 is stored.
please note that I am using two different methods of counting lines, grep and sed. in both cases they return 3 instead of the correct answer, 2.
here is a consolidated example to try in a test.sh file:
echo -n "without assignment: "
ps ax | grep -c "$0"
var=$(ps ax | grep "$0" | sed -n '$=')
echo "using sed method: $var"
var=`ps ax | grep -c "$0"`
echo "using grep method: $var"
the results on my debian box:
without assignment: 2
using sed method: 3
using grep method: 3
the questions again: why is this happening, and how to prevent or work around?
Quoting Siegex:
Because the grep process itself is being returned by ps.
You can either of these:
"trick" grep to not match itself by surrounding one of the search
characters in a character class [ ] which doesn't change the
functionality:
Or, in this case,
Pipe to grep -v grep, so that the process doesn't match:
var=$(ps ax | grep -v grep | grep "$0")
See an example. Here we have a process sleep:
$ sleep 20 &
[1] 5602
If we check for it in the output of ps it appears twice!
$ ps -ef| grep sleep
me 5602 5433 0 09:49 pts/2 00:00:00 sleep 20
me 5607 5433 0 09:49 pts/2 00:00:00 grep --colour=auto sleep
So we can either use a character class:
$ ps -ef| grep [s]leep
me 5602 5433 0 09:49 pts/2 00:00:00 sleep 20
Or grep out the grep process:
$ ps -ef| grep sleep | grep -v grep
me 5602 5433 0 09:49 pts/2 00:00:00 sleep 20
Command substitution itself runs in a subshell so thats one bash process
your search for bash ($0) i.e. grep -c bash also ends up in the process table at that time so thats another process (grep) containing string bash. Note that, this might not show up in the process table at the time of running, depending on how busy your system is.
And you have two (or whatever) actual bash processes (sessions) running presumably are the rest
You can use a Regex trick to get rid of the false positive i.e. grep one from count:
ps ax | grep -c "[b]ash"
It would still count the subshell while doing command substitution:
var=$(ps ax | grep -c "[b]ash")
So you need to manually remove one from this count.
Example:
$ var=$(ps ax | grep -c "bash")
$ echo $var
4
$ var=$(ps ax | grep -c "[b]ash")
$ echo $var
3
Your command counts the grep command line too.
ps ax | grep -v grep | grep -c "$0"
should omit the grep from the count

Bash script: argument not found

I have a small bash script (check_status) by which I am trying to know if a process is running or not.
#!/bin/bash
# check argument
if ["$1" == ""];
then
echo "Invalid argument"
exit 3
fi
PN=$(ps -ef | grep $1 | wc -l)
echo "process is $1: executing $PN"
if [ $PN -gt 1 ];
then
status=OK
message=UP
exit=0
else
status=CRITICAL
message=DOWN
exit=2
fi
echo "$status - $1 is $message"
exit $exit
However, when I run this from shell sh checkstatus xyz I get this:
check_status: 3: check_status: [xyz: not found
process is xyz: executing 3
OK - xyz is UP
Now, my first problem is the check_status: 3: check_status: [xyz: not found error. I dont know why its showing up.
Next,there is no process xyz running in my server. So, as per my understanding I am running ps -ef | grep xyz | wc -l in the shell which should echo 1 if no process is running. But, I am getting 3.
How do I fix this?
Update
I changed if ["$1" == ""]; to if [ "$1" = "" ] Now I am not getting the error. But still my PN=$(ps -ef | grep $1 | wc -l) is returning 3.
I then updated PN=$(ps -ef | grep $1 | wc -l) to PN=$(ps -ef | grep $1 ) which gave me the following response:
admin 14674 4570 0 12:03 pts/2 00:00:00 sh check_status xyz
admin 14675 14674 0 12:03 pts/2 00:00:00 sh check_status xyz
admin 14677 14675 0 12:03 pts/2 00:00:00 grep xyz
One sh check_status xyz and one grep xyz makes seance to me. But, any idea why I see two of them ?
(1) As mentioned elsewhere, you'll need spaces around "[" and "]".
(2) If your ps supports the -c option, you should consider using it. Otherwise, if you use ps, you will need to parse the output in some way. (You might want to insert "| tee /dev/tty" to see what your ps command is producing.) But is grep (or pgrep) really what you want here? The messages your script is producing suggest otherwise.
(3) If, for example, you want an exact match of the basename, consider the following (which is broken down into separate steps so you can more easily adapt it to your purpose):
ps -c | awk '{print $4}' | grep "^$x\$"
Other than the spaces around [ that others have mentioned, you should change this:
PN=$(ps -ef | grep $1 | wc -l)
to this:
PN=$(pidof $1 | wc -w)
That will get you a count of running processes that match the name you specified.
The reason you're getting a greater count than expected from your original code is because the grep command also adds one to the count, and it will also produce a hit on any other process that might happen to contain the same characters in its name as the process being targeted. Using pidof eliminates both of these factors.

bash script inside here document not behaving as expected

Here is a minimal test case which fails
#!/bin/tcsh
#here is some code in tcsh I did not write which spawns many processes.
#let us pretend that it spawns 100 instances of stupid_test which the user kills
#manually after an indeterminate period
/bin/bash <<EOF
#!/bin/bash
while true
do
if [[ `ps -e | grep stupid_test | wc -l` -gt 0 ]]
then
echo 'test program is still running'
echo `ps -e | grep stupid_test | wc -l`
sleep 10
else
break
fi
done
EOF
echo 'test program finished'
The stupid_test program is consists of
#!/bin/bash
while true; do sleep 10; done
The intended behavior is to run until stupid_test is killed (in this case manually by the user), and then terminate within the next ten seconds. The observed behavior is that the script does not terminate, and evaluates ps -e | grep stupid_test | wc -l == 1 even after the program has been killed (and it no longer shows up under ps)
If the bash script is run directly, rather than in a here document, the intended behavior is recovered.
I feel like I am doing something very stupidly wrong, I am not the most experienced shell hacker at all. Why is it doing this?
Usually when you try to grep the name of a process, you get an extra matching line for grep itself, for example:
$ ps xa | grep something
57386 s002 S+ 0:00.01 grep something
So even when there is no matching process, you will get one matching line. You can fix that by adding a grep -v grep in the pipeline:
ps -e | grep stupid_test | grep -v grep | wc -l
As tripleee suggested, an even better fix is writing the grep like this:
ps -e | grep [s]tupid_test
The meaning of the pattern is exactly the same, but this way it won't match grep itself anymore, because the string "grep [s]tupid_test" doesn't match the regular expression /[s]tupid_test/.
Btw I would rewrite your script like this, cleaner:
/bin/bash <<EOF
while :; do
s=$(ps -e | grep [s]tupid_test)
test "$s" || break
echo test program is still running
echo "$s"
sleep 10
done
EOF
Or a more lazy but perhaps sufficient variant (hinted by bryn):
/bin/bash <<EOF
while ps -e | grep [s]tupid_test
do
echo test program is still running
sleep 10
done
EOF

Can't understand ps | wc output differences

I was trying to write a set of functions that could check to see if a process name was running when I encountered some unexpected output. I've condensed the issue in the following script names isRunning.sh which depends on a system ps command that can take the '-fC' arguments...
#!/bin/bash
progname=isRunning.sh
ps -fC isRunning.sh
pRet=`ps -fC ${progname} | wc -l`
echo pRet $pRet
psOut=`ps -fC ${progname}`
wcOut=`echo "${psOut}" | wc -l`
echo
echo ps output
echo "${psOut}"
echo
echo wcOut $wcOut
The first attempt at piping the ps output to wc gets a return of 3. The second attempt gets the expected return value of 2. Can anyone explain this behavior? I figure it's got to be something stupid I am overlooking.
Thanks,
bbb
edit: my output
UID PID PPID C STIME TTY TIME CMD
root 6717 5940 0 13:10 pts/0 00:00:00 /bin/bash ./isRunning.sh
pRet 3
ps output
UID PID PPID C STIME TTY TIME CMD
root 6717 5940 0 13:10 pts/0 00:00:00 /bin/bash ./isRunning.sh
wcOut 2
I get 2 both attempts. Your ps might be outputting an extra blank line, or somesuch, and then your shell's backtick expansion stripping it. Or maybe you actually had two processes matching the first time you ran it.
If you just want to see if its running, check the exit code from your ps:
if ps -C "${progname}" > /dev/null; then
echo its running
else
echo not running
fi
Even better, you should take a look at pidof and pgrep if you can rely on them being present on whichever systems you're targeting. Or use the LSB functions, if you're on Linux.
edit: Actually, since you're looking for copies of yourself running, you might be picking up the shell doing a fork to implement the pipe.

Resources