bash fork subprocess with command link $(cmd1|cmd2) - bash

Question
I wrote some command lines like output=$(cmd1|cmd2) in a bash script file (aaaa.sh) and found a subprocess generated.
code in the file aaaa.sh
echo "The name of this file is $(basename $0)."
echo "The pid of this program is $$."
echo -e '\n---- 0 ----'
echo -e 'CMD: ps -ef | grep "aaaa.sh" | grep -v grep'
ps -ef | grep "aaaa.sh" | grep -v grep
echo -e '\n---- 1 ----'
echo -e 'CMD: ps -ef | grep "aaaa.sh" | grep -v "bbbb" | grep -v grep'
ps -ef | grep "aaaa.sh" | grep -v "bbbb" | grep -v grep
echo -e '\n---- 2 ----'
echo -e 'CMD: pgrep -a "aaaa.sh" | grep -v "bbbb"'
pgrep -a "aaaa.sh" | grep -v "bbbb"
echo -e '\n---- 3 ----'
echo -e 'CMD: pgrep -a "aaaa.sh" | grep -v "$$"'
pgrep -a "aaaa.sh" | grep -v "$$"
echo -e '\n---- 4 ----'
echo -e 'CMD: output=$(ps -ef | grep "aaaa.sh" | grep -v "bbbb" | grep -v grep)'
output=$(ps -ef | grep "aaaa.sh" | grep -v "bbbb" | grep -v grep)
echo -e "$output"
echo -e '\n---- 5 ----'
echo -e 'CMD: output=$(ps -ef | grep "aaaa.sh" | grep -v "$$" | grep -v grep)'
output=$(ps -ef | grep "aaaa.sh" | grep -v "$$" | grep -v grep)
echo -e "$output"
echo -e '\n---- 6 ----'
echo -e 'CMD: output=$(pgrep -a "aaaa.sh" | grep -v "bbbb")'
output=$(pgrep -a "aaaa.sh" | grep -v "bbbb")
echo -e "$output"
echo -e '\n---- 7 ----'
echo -e 'CMD: output=$(pgrep -a "aaaa.sh" | grep -v "$$")'
output=$(pgrep -a "aaaa.sh" | grep -v "$$")
echo -e "$output"
echo -e '\n---- 8 ----'
echo -e 'CMD: output=$(pgrep -a "aaaa.sh")'
output=$(pgrep -a "aaaa.sh")
echo -e "$output"
output
output
Question
There is a subprocess generated in 4,5,6 and 7. why?

Shells typically execute command substitution ($()) and command piping (|) in subshells.
output=$(ps -ef | grep "aaaa.sh" | grep -v "$$" | grep -v grep)
This statement actually results in the creation of five processes--one for the command substitution subshell, and one more for each of the commands in the pipeline.
From the bash man page:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
Edit - To try this out for yourself run the following:
$ echo $BASHPID >&2 | echo $BASHPID >&2 | echo $BASHPID
We can see a different PID for each subshell in the pipeline.

That's probably because of
$(...)
Quote from bash man page:
Command substitution allows the output of a command to replace the command name. There are two forms:
$(command)
or
`command`.
Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during
word splitting.
So, you actually see the subshell created to execute your commands

Related

Bash if -gt also triggered when values equal?

I want my script to check if it's already running in another instance:
$ cat test.sh
#!/bin/bash
ps -ef | grep -v grep | grep -i "test.sh" | grep bash
ps -ef | grep -v grep | grep -i "test.sh" | grep -c bash
if [ `ps -ef | grep -v grep | grep -i "test.sh" | grep -c bash` -gt 1 ]; then echo "There's another instance running."
else echo "Only this instance is running."
fi
However the output is
$ ./test.sh
noes 9503 7494 0 09:32 pts/1 00:00:00 /bin/bash ./test.sh
1
There's another instance running.
Clearly, 1 is not greater than 1, so why is the if condition triggered?
Thanks
From test man page:
INTEGER1 -gt INTEGER2
INTEGER1 is greater than INTEGER2
So the answer is no, -gt is not triggered when values are equal. In fact, as you can see if you modify the script in this way:
$ cat test.sh
#!/bin/bash
ps -ef | grep -v grep | grep -i "test.sh" | grep bash
ps -ef | grep -v grep | grep -i "test.sh" | grep -c bash
STRINGS=`ps -ef | grep -v grep | grep -i "test.sh"`
echo "$STRINGS"
COUNT=`ps -ef | grep -v grep | grep -i "test.sh" | grep -c bash`
echo $COUNT
if [ `ps -ef | grep -v grep | grep -i "test.sh" | grep -c bash` -gt 1 ]; then echo "There's another instance running."
else echo "Only this instance is running."
fi
You get this:
$ ./test.sh
lucio 5097 4736 0 10:10 pts/2 00:00:00 /bin/bash ./test.sh
1
lucio 5097 4736 0 10:10 pts/2 00:00:00 /bin/bash ./test.sh
lucio 5106 5097 0 10:10 pts/2 00:00:00 /bin/bash ./test.sh
2
There's another instance running.
If you modify the script in this way, it will work:
#!/bin/bash
ps -ef | grep -v grep | grep -i "test.sh" | grep bash
pgrep -c test.sh
if [ $(pgrep -c test.sh) -gt 1 ]; then
echo "There's another instance running."
else
echo "Only this instance is running."
fi
This is the output:
$ ./test.sh
lucio 5197 4736 0 10:17 pts/2 00:00:00 /bin/bash ./test.sh
1
Only this instance is running.
Note the use $() instead of backticks. Check this answer for this change.

Shell exec command with | fails

I write a script with | in command , it run fail .
#! /bin/bash
datestr=`date -d '-1 day' '+%Y-%m-%d'`
cmd="cat /service/domain_detect/api/storage/logs/laravel.log | grep sub_domain | grep $datestr | wc -l"
echo $cmd
count=$($cmd)
echo $count
Expected output is the result line num.
The actual result is follow:
cat /service/domain_detect/api/storage/logs/laravel.log | grep sub_domain | grep 2019-01-17 | wc -l
cat: invalid option -- 'l'
Try 'cat --help' for more information.

Bash outputting kill usage

I have a bash script as follows:
if [[ "$1" == "stop" ]]; then
echo "[$(date +'%d/%m/%Y %H:%M:%S:%s')]: Killing all active watchers" >> $LOG
kill -9 $(ps -ef | grep "processname1" | grep -v "grep" | grep -v "$$" | awk
'{print $2}' | xargs)
echo "[$(date +'%d/%m/%Y %H:%M:%S:%s')]: Killing all current processname2
processes" >> $LOG
kill -9 $(ps -ef | grep "processname2" | grep -v "grep" | awk '{print $2}' |
xargs)
exit 0
when i run 'x service stop', the following is outputted:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill
-l [sigspec]
Killed
How do i stop the kill usage being displayed? It is successfully killing the process, however the fact that the usage is displayed is causing AWS CodeDeploy to fail.
Thanks!
Adam, please note that this is really just a comment with formatting. Don't take this as a real answer to your question. Please focus on the constructive comments to your question.
In my mis-spent youth, I wrote this bash function to do the ps -ef | grep .... madness:
# ps-grep
psg() {
local -a patterns=()
(( $# == 0 )) && set -- $USER
for arg do
patterns+=("-e" "[${arg:0:1}]${arg:1}")
done
ps -ef | grep "${patterns[#]}"
}
using the knowledge that the pattern [p]rocessname will not match the string [p]rocessname

Sed finding itself and throwing off script results

The script:
#!/bin/bash
SERVICE="$1"
RESULT=`ps -a | sed -n /${SERVICE}/p`
MEM=$(ps aux | sort -rk +4 | grep $1 | grep -v grep | awk '{print $4}' | awk 'NR == 1')
if [ "${RESULT:-null}" = null ]; then
echo "$1 is NOT running"
else
echo "$MEM"
fi
if [ "$MEM" -ge 1 ]; then
mailx -s "Alert: server needs to be checked" me#admins.com
fi
The problem with the ps output piped to sed is even if no process is running it finds itself:
ps aux | sed -n /nfdump/p
root 30724 0.0 0.0 105252 884 pts/1 S+ 14:16 0:00 sed -n /process/p
and them my script bypasses the expected result of "service is not running" and goes straight to echo'ing $MEM, which in this case will be 0.0. Does sed have a grep -v grep eqivalent to get itself out of the way?
Let me add one more example in addition to my comments above. Sed has indeed an equivalent to grep -v: You can negate a match with /RE/! (append a ! to the regex) thus you can try:
ps aux | sed -n '/sed/!{ /nfdump/ p;}'
Here the part inside { ... } is only applied to lines not matching sed.
For the record: there is a command pgrep that can replace your ps sed pipeline, see pgrep in Wikipedia or its manpage.
Try :
ps aux | sed -n '/[n]fdump/p'
or :
ps aux | grep '[n]fdump'
The regex won't be find in the processes list

not a valid identifier - bash script error while execution

I'm getting following error while I try to capture process ids in my shell script.....
$bash ./restartjbossserver.sh
./restartjbossserver.sh: line 10: `i=$(ps -ef | grep "jboss" | grep -v "grep" | awk '{print $2}')': not a valid identifier
And this is my script....
for i=$(ps -ef | grep "jboss" | grep -v "grep" | awk '{print $2}')
do
echo $i
if [ $i != NULL ]
then
echo "Killing JBos Process.."
kill -9 $i
echo "Killed Joss Process..."
fi
done
sleep 10s
echo "Deleting JBoss Cache..."
rm -rf /home/cbsmsblapp/opt/EAP-6.3.0/jboss-eap-6.3/domain/tmp/*
echo " Deleted JBoss Cache..."
sleep 10s
nohup /home/cbsmsblapp/opt/EAP-6.3.0/jboss-eap-6.3/bin/domain.sh & >nohup.out
The syntax for iterating over a list is
for i in $( ...
not
for i=$( ...
Have a look at the pkill and pgrep commands. You could just pkill jboss.

Resources