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
Related
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.
I've a scirpt name server.sh
#!/bin/bash
process_count=$(ps aux | grep server.sh | grep -v grep | wc -l )
echo "total process running:"
echo $process_count
... other script code
when I run script I get output as
./server.sh
total process running:
2
Why do I get process count as 2 instead of 1? I only have one script running and have also excluded grep process.
Even using pgrep -f server.sh and excluding pgrep gives 2 as process count.
My approach is to redirect the output of ps into a temp file, then grep that file. That will eliminate extra processes.
ps aux > /tmp/ps.txt
process_count=$(grep server.sh /tmp/ps.txt | wc -l)
rm /tmp/ps.txt
I am trying to get the PID of the command within the quotes (e.g. some-command-here) whilst being able to interact with the process spawned by some-command-here:
x-terminal-emulator -e "some-command-here" &> /dev/null
For example, sometimes apt full-upgrade -y can require user input, thus it is important that the process spawned by some-command-here remain interactive.
x-terminal-emulator -e "some-command-here & echo $! >/tmp/pid" &> /dev/null
cat /tmp/pid
... or get rid of the &>/dev/null:
x-terminal-emulator -e "some-command-here & echo $!"
So, with a bit more testing and research, it seems that this task can be accomplished utilizing ps ax, grep, xargs & cut.
ps ax | grep -v "grep" | grep "sh -c" | grep "some-command-here" | xargs | cut -d ' ' -f 1
🤔How does this work?
When x-terminal-emulator is passed -e it spawns a new shell window and passes it the command within the quotes. In order to execute the command, it passes the new shell the command sh -c along with the quoted command. Thus, sh -c some-command-here. Which is utilized within the searching of the PID above.
ps ax lists the currently running processes.
grep -v "grep" searches the output and removes any processes that contain grep as a process simply because when we launch this query, a process under grep will be created.
grep "sh -c" searches the output for a process that contains sh -c.
grep "some-command-here" searches the output of ps ax for a process that matches what is within the quotes.
xargs converts the output of grep "sh -c some-command-here" into a space delimited list.
cut -d ' ' -f 1 removes the spaces and grabs the first result. Which returns the PID of the process in question.
When I run the following code in a bash script I receive an output of 2
#!/bin/bash
HIPPO=$(ps -a | grep hippo | wc -l)
echo "$HIPPO"
However when I run the command ps -a | grep hippo | wc -l straight from a command prompt I get an output of 0
Reading the documentation on ps particularly the -a flag, I'm not understanding why the output is different.
How is called your script? If you named it with hippo, it will count in your ps call.
https://superuser.com/questions/935374/difference-between-and-in-shell-script
When you do the command substitution, the command gets runs once according to the above. So I am assuming, the echo is picking a zombie process that ran that command.
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