How to find the number of instances of current script running in bash? - bash

I have the below code to find out the number of instances of current script running that is running with same arg1. But looks like the script creates a subshell and executes this command which also shows up in output. What would be the better approach to find the number of instances of running script ?
$cat test.sh
#!/bin/bash
num_inst=`ps -ef | grep $0 | grep $1 | wc -l`
echo $num_inst
$ps aux | grep test.sh | grep arg1 | grep -v grep | wc -l
0
$./test.sh arg1 arg2
3
$
I am looking for a solution that matches all running instance of ./test.sh arg1 arg2 not the one with ./test.sh arg10 arg20

The reason this creates a subshell is that there's a pipeline inside the command substitution. If you run ps -ef alone in a command substitution, and then separately process the output from that, you can avoid this problem:
#!/bin/bash
all_processes=$(ps -ef)
num_inst=$(echo "$all_processes" | grep "$0" | grep -c "$1")
echo "$num_inst"
I also did a bit of cleanup on the script: double-quote all variable references to avoid weird parsing, used $() instead of backticks, and replaced grep ... | wc -l with grep -c.
You might also replace the echo "$all_processes" | ... with ... <<<"$all_processes" and maybe the two greps with a single grep -c "$0 $1":
...
num_inst=$(grep -c "$0 $1" <<<"$all_processes")
...

Modify your script like this:
#!/bin/bash
ps -ef | grep $0 | wc -l
No need to store the value in a variable, the result is printed to standard out anyway.
Now why do you get 3?
When you run a command within back ticks (fyi you should use syntax num_inst=$( COMMAND ) and not back ticks), it creates a new sub-shell to run COMMAND, then assigns the stdout text to the variable. So if you remove the use of $(), you will get your expected value of 2.
To convince yourself of that, remove the | wc -l, you will see that num_inst has 3 processes, not 2. The third one exists only for the execution of COMMAND.

Related

Bash - Two processes for one script

I have a shell script, named test.sh :
#!/bin/bash
echo "start"
ps xc | grep test.sh | grep -v grep | wc -l
vartest=`ps xc | grep test.sh | grep -v grep | wc -l `
echo $vartest
echo "end"
The output result is :
start
1
2
end
So my question is, why are there two test.sh processes running when I call ps using `` (the same happens with $()) and not when I call ps directly?
How can I get the desired result (1)?
When you start a subshell, as with the backticks, bash forks itself, then executes the command you wanted to run. You then also run a pipeline which causes all of those to be run in their own subshells, so you end up with the "extra" copy of the script that's waiting for the pipeline to finish so it can gather up the output and return that to the original script.
We'll do a little expermiment using (...) to run processes explicitly in subshells and using the pgrep command which does ps | grep "name" | grep -v grep for us, just showing us the processes that match our string:
echo "Start"
(pgrep test.sh)
(pgrep test.sh) | wc -l
(pgrep test.sh | wc -l)
echo "end"
which on a run for me produces the output:
Start
30885
1
2
end
So we can see that running pgrep test.sh in a subshell only finds the single instance of test.sh, even when that subshell is part of a pipeline itself. However, if the subshell contains a pipeline then we get the forked copy of the script waiting for the pipeline to finish

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 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

Testing if a Daemon is alive or not with Shell

I have a log_sender.pl perl script that when executed runs a daemon. I want to make a test, using Shell:
#!/bin/bash
function log_sender()
{
perl -I $HOME/script/log_sender.pl
}
(
[[ "${BASH_SOURCE[0]}" == "${0}" ]] || exit 0
function check_log_sender()
{
if [ "ps -aef | grep -v grep log_sender.pl" ]; then
echo "PASSED"
else
echo FAILED
fi
}
log_sender
check_log_sender
)
Unfortunately when I run this my terminal becomes:
-bash-4.1$ sh log_sender.sh
...
...
What am I doing wrong?
> if [ "ps -aef | grep -v grep log_sender.pl" ]; then
This is certainly not what you want. Try this:
if ps -aef | grep -q 'log_sender\.pl'; then
...
In a shell script, the if construct takes as its argument a command whose exit status it examines. In your code, the command is [ (also known as test) and you run it on the literal string "ps -aef | grep -v grep log_sender.pl" which is simply always true.
You probably intended to check whether ps -aef outputs a line which contains log_sender.pl but does not contain grep; that would be something like ps -aef | grep -v grep | grep 'log_sender\.pl' but you can avoid the extra grep -v by specifying a regular expression which does not match itself.
The -q option to grep suppresses any output; the exit code indicates whether or not the input matched the regular expression.
The perl invocation is also not correct; the -I option requires an argument, so you are saying effectively just perl and your Perl interpreter is now waiting for you to type in a Perl script for it to execute. Apparently the script is log_sender.pl so you should simply drop the -I (or add an argument to it, if you really do need to add some Perl library paths in order for the script to work).
Finally, if you write a Bash script, you should execute it with Bash.
chmod +x log_sender.sh
./log_sender.sh
or alternatively
bash ./log_sender.sh
The BASH_SOURCE construct you use is a Bashism, so your script will simply not work correctly under sh.
Finally, the parentheses around the main logic are completely redundant. They will cause the script to run these commands in a separate subshell for no apparent benefit.

all users executing a certain command. grep

I'd like to find all the names of the users that are executing a certain command given as a parameter.
grep must be used.
I have tried: ps aux | grep $1 | cut -d" " -f1, but it's not the desired result.
/usr/ucb/ps aux | awk '/<your_command_as_parameter>/{print $1}'|sort -u
for eg:
> /usr/ucb/ps aux | awk '/rlogin/{print $1}' | sort -u
I guess you're looking for this.
# cat test.sh
ps aux | grep $1 | grep -v grep | awk '{print $1}'
# ./test.sh bash
root
root
root
There is a trick to getting the information for processes but not the process that's searching for the process, which is to make the name into a regex. For example, if you're searching for ls, make the search term into grep '[l]s'. This works unless you're searching for grep itself, or a single-letter command name.
This is the procname script I use; it works with most POSIX shells:
#! /bin/ksh
#
# #(#)$Id: procname.sh,v 1.3 2008/12/16 07:25:10 jleffler Exp $
#
# List processes with given name, avoiding the search program itself.
#
# If you ask it to list 'ps', it will list the ps used as part of this
# script; if you ask it to list 'grep', it will list the grep used as
# part of this process. There isn't a sensible way to avoid this. On
# the other hand, if you ask it to list httpd, it won't list the grep
# for httpd. Beware metacharacters in the first position of the
# process name.
case "$#" in
1)
x=$(expr "$1" : '\(.\).*')
y=$(expr "$1" : '.\(.*\)')
ps -ef | grep "[$x]$y"
;;
*)
echo "Usage: $0 process" 1>&2
exit 1
;;
esac
In bash, you could use the variable substringing operations to avoid the expr commands:
case "$#" in
1) ps -ef | grep "[${1:0:1}]${1:1}"
;;
*)
echo "Usage: $0 process" 1>&2
exit 1
;;
esac
Both of these run ps -ef; you can use ps aux if you prefer. The search for the 'command' name is not constrained to the command portion of the command, so you could use procname root to find processes run by root. The match is also not constrained to a full word; you could consider grep -w for that (a GNU grep extension).
The output of these is the full line of data from ps; if you want just the user (the first field), then pipe the output to awk '{print $1}' | sort -u.

Resources