bash command substitution's result not consistent - bash

I have a script.
cat /root/test/ddd.sh
#!/bin/bash
s=/root/test/ddd.sh
ps -ef | grep $s | grep -v grep
result=$(ps -ef | grep $s | grep -v grep | wc -l)
echo $result
when i execute it, the result is weird, it shows that there is two line matched.
[root#l2 test]# /root/test/ddd.sh
root 15361 15032 0 09:52 pts/18 00:00:00 /bin/bash /root/test/ddd.sh
2

That's because you're running a subshell. That is, the $(...) piece causes bash to fork, thereby creating two (nearly) identical processes. By identical, I mean basically everything except for process ID, parent process ID, return code from the fork, and I can't think of anything else. But one thing that does remain the same for sure is the command line. Both of them will be "/bin/bash /root/test/ddd.sh". So, inside the result=$(...), there will be exactly one extra process that matches.
You can see this, for example, by removing the | wc -l piece at the end of your $(...), and, to make it more readable, enclose the echo's argument in quotes:
result="$(ps -ef | grep $s | grep -v grep)"
echo "$result"
Here you will see that there are two bashes, and the PPID of one is the PID of the other, showing the parent-child relationship.

Related

need to check wheter multiple pid are finished execution

pid_list="21232 21231 43432" # same can be put in an array as well
I am running a bunch of commands in the background and obtained its pids using $i. How can i check whether it is finished or not.
Try this:
ps -ef | egrep $(echo $pid_list | sed 's/ /|/g') | grep -v grep | wc -l
If the output is 0, that means the processes are finished.
There is a way to query ps for information about a specific PID, or a list of PIDs.
This should do the trick in your case. If this command returns anything other than zero, then at least one of the PIDs on the list is still active/not finished.
ps -p $(echo ${pid_list} | sed 's/\ /,/g') | tail -n +2 | wc -l
Explanation:
ps -p accepts a comma-delimited list of PIDs to get info for.
sed 's/\ /,/g' replaces space delimiters with commas
tail -n +2 removes the first line in the output, which are only headers
wc -l counts the number of lines that are returned.

Complicated grep commands not executing in shell script

I am trying to execute a couple of complicated grep commands via a shells script that work fin in the terminal manually executed. I can't for the life of me figure out why this doesn't work.
The goal of the first grep is to get out any process id attached to the parent myPattern. The 2nd get the process id of the process myPattern
Currently my shell script
returns nothing for the 1st.
ignores the "grep -v 'grep'" part in the 2nd.
#!/bin/sh
ps -ef | grep "$(ps -ef | grep 'myPattern' | grep -v grep | awk '{print $2}')" | grep -v grep | grep -v myPattern | awk '{print $2}'
ps -ef | grep 'myPattern' | grep -v 'grep' | awk '{print $2}'
This works fine when run in the terminal manually. Any ideas where i have stuffed this up?
Your first command is vague. I don't think it would reliably do what you describe. It also does not guard against getting the id of the first grep call. The second one works for me. For the fists query it highly depends on the system you are using. It's easier to use pstree to show you the whole process tree under a pid. Like:
pstree -p 1782 | sed 's/-/\n/g' | sed -n -e 's/.*(\([0-9]\+\)).*/\1/p'
You need to limit pid to be a single value. If you have more values, then you have to loop through them. If you don't have pstree, then you can craft some loop around ps. Make note that even if you current commands worked, then thwy would catch only one level parent/child relationships. pstree does any level.
I also have to tell you that a process can escape original parent as a parent process by forking.
In any case without exact details what you are trying to achieve and why, and on what platform it is hard to give you a great answer. Also these utilities albeit present virtually everywhere are not as portable as one would wish.
One more note - /bin/sh is often not your current shell. On many linux systems user has a default shell of bash and /bin/sh is dash or some other shell variant. So if you see diffs with what you have in console and script, it can be difference in actual shell you are using.
Based on user feedback it would be much easier to have something like this in the java process launching script:
java <your params here> &
echo $! > /var/run/myprog.pid
Then the kill script would look like echo /var/run/myprog.pid | xargs kill. There are shorter commands but I think this is more portable. Give actual code if you want more specific.

Shell scripting obtain command PID

In a shell script lets say i have run a command like this
for i in `ps -ax|grep "myproj"`
do
echo $i
done
Here, the grep command would be executed as a separate process. Then how do i get its PID in the shell script ?
I'm going out on a limb here, and understand this looks more like a comment.
Why do you need the PID of the grep command?
In your comment you say you want to compare it in the loop against something. I would suppose that it is your issue that that the loop will (sometimes) not only include myproj but also an item about your grep command? If so, try the following:
for i in `ps -ax | grep -v grep | grep "myproj"`
do
echo $i
done
The -v switch basically inverts the pattern, so grep -v grep (or grep -v "grep", which maybe looks a bit less awkward) will include only lines that do not include the string "grep" (see man grep).
Note that this maybe overly vague for some cases, for example if the pattern you actually look for also contains the string "grep". For example, the following might not work as you'd expect: ps -ax | grep -v grep | grep mygrepling
However, in your particular case, where you only look for "myproj" it will do.
Or you could simply use
for i in `ps -ax | grep "my[p]roj"`
do
echo $i
done
That way there is no need to know the PID of the grep command, because it simply never shows up as a loop iteration.
When you run a process in background, you can get its PID in $!
$ ps aux | grep dddddd & echo $!
[1] 27948
27948
ic 27948 0.0 0.0 3932 760 pts/3 R 08:49 0:00 grep dddddd
When in foreground --- the process does not exist anymore at the point you want to find its PID. When you are in the loop, the for statement is already executed and grep is already exited, so you can not find its PID anymore.

Use output of bash command (with pipe) as a parameter for another command

I'm looking for a way to use the ouput of a command (say command1) as an argument for another command (say command2).
I encountered this problem when trying to grep the output of who command but using a pattern given by another set of command (actually tty piped to sed).
Context:
If tty displays:
/dev/pts/5
And who displays:
root pts/4 2012-01-15 16:01 (xxxx)
root pts/5 2012-02-25 10:02 (yyyy)
root pts/2 2012-03-09 12:03 (zzzz)
Goal:
I want only the line(s) regarding "pts/5"
So I piped tty to sed as follows:
$ tty | sed 's/\/dev\///'
pts/5
Test:
The attempted following command doesn't work:
$ who | grep $(echo $(tty) | sed 's/\/dev\///')"
Possible solution:
I've found out that the following works just fine:
$ eval "who | grep $(echo $(tty) | sed 's/\/dev\///')"
But I'm sure the use of eval could be avoided.
As a final side node: I've noticed that the "-m" argument to who gives me exactly what I want (get only the line of who that is linked to current user). But I'm still curious on how I could make this combination of pipes and command nesting to work...
One usually uses xargs to make the output of one command an option to another command. For example:
$ cat command1
#!/bin/sh
echo "one"
echo "two"
echo "three"
$ cat command2
#!/bin/sh
printf '1 = %s\n' "$1"
$ ./command1 | xargs -n 1 ./command2
1 = one
1 = two
1 = three
$
But ... while that was your question, it's not what you really want to know.
If you don't mind storing your tty in a variable, you can use bash variable mangling to do your substitution:
$ tty=`tty`; who | grep -w "${tty#/dev/}"
ghoti pts/198 Mar 8 17:01 (:0.0)
(You want the -w because if you're on pts/6 you shouldn't see pts/60's logins.)
You're limited to doing this in a variable, because if you try to put the tty command into a pipe, it thinks that it's not running associated with a terminal anymore.
$ true | echo `tty | sed 's:/dev/::'`
not a tty
$
Note that nothing in this answer so far is specific to bash. Since you're using bash, another way around this problem is to use process substitution. For example, while this does not work:
$ who | grep "$(tty | sed 's:/dev/::')"
This does:
$ grep $(tty | sed 's:/dev/::') < <(who)
You can do this without resorting to sed with the help of Bash variable mangling, although as #ruakh points out this won't work in the single line version (without the semicolon separating the commands). I'm leaving this first approach up because I think it's interesting that it doesn't work in a single line:
TTY=$(tty); who | grep "${TTY#/dev/}"
This first puts the output of tty into a variable, then erases the leading /dev/ on grep's use of it. But without the semicolon TTY is not in the environment by the moment bash does the variable expansion/mangling for grep.
Here's a version that does work because it spawns a subshell with the already modified environment (that has TTY):
TTY=$(tty) WHOLINE=$(who | grep "${TTY#/dev/}")
The result is left in $WHOLINE.
#Eduardo's answer is correct (and as I was writing this, a couple of other good answers have appeared), but I'd like to explain why the original command is failing. As usual, set -x is very useful to see what's actually happening:
$ set -x
$ who | grep $(echo $(tty) | sed 's/\/dev\///')
+ who
++ sed 's/\/dev\///'
+++ tty
++ echo not a tty
+ grep not a tty
grep: a: No such file or directory
grep: tty: No such file or directory
It's not completely explicit in the above, but what's happening is that tty is outputting "not a tty". This is because it's part of the pipeline being fed the output of who, so its stdin is indeed not a tty. This is the real reason everyone else's answers work: they get tty out of the pipeline, so it can see your actual terminal.
BTW, your proposed command is basically correct (except for the pipeline issue), but unnecessarily complex. Don't use echo $(tty), it's essentially the same as just tty.
You can do it like this:
tid=$(tty | sed 's#/dev/##') && who | grep "$tid"

How to get process id from process name?

I'm trying to create a shell script getting the process id of the Skype app on my Mac.
ps -clx | grep 'Skype' | awk '{print $2}' | head -1
The above is working fine, but there are two problems:
1)
The grep command would get all process if their name just contains "Skype". How can I ensure that it only get the result, if the process name is exactly Skype?
2)
I would like to make a shell script from this, which can be used from the terminal but the process name should be an argument of this script:
#!/bin/sh
ps -clx | grep '$1' | awk '{print $2}' | head -1
This isn't returning anything. I think this is because the $2 in the awk is treated as an argument too. How can I solve this?
Your ps -cl1 output looks like this:
UID PID PPID F CPU PRI NI SZ RSS WCHAN S ADDR TTY TIME CMD
501 185 172 104 0 31 0 2453272 1728 - S ffffff80145c5ec0 ?? 0:00.00 httpd
501 303 1 80004004 0 31 0 2456440 1656 - Ss ffffff8015131300 ?? 0:11.78 launchd
501 307 303 4004 0 33 0 2453456 7640 - S ffffff8015130a80 ?? 0:46.17 distnoted
501 323 303 40004004 0 33 0 2480640 9156 - S ffffff80145c4dc0 ?? 0:03.29 UserEventAgent
Thus, the last entry in each line is your command. That means you can use the full power of regular expressions to help you.
The $ in a regular expression means the end of the string, thus, you could use $ to specify that not only does the output must have Skype in it, it must end with Skype. This means if you have a command called Skype Controller, you won't pull it up:
ps -clx | grep 'Skype$' | awk '{print $2}' | head -1
You can also simplify things by using the ps -o format to just pull up the columns you want:
ps -eo pid,comm | grep 'Skype$' | awk '{print $1}' | head -1
And, you can eliminate head by simply using awk's ability to select your line for you. In awk, NR is your record number. Thus you could do this:
ps -eo pid,comm | grep 'Skype$' | awk 'NR == 1 {print $1}'
Heck, now that I think of it, we could eliminate the grep too:
ps -eo pid,comm | awk '/Skype$/ {print $1; exit}'
This is using awk's ability to use regular expressions. If the line contains the regular expression, 'Skype$', it will print the first column, then exit
The only problem is that if you had a command Foo Skype, this will also pick it up. To eliminate that, you'll have to do a bit more fancy footwork:
ps -eo pid,comm | while read pid command
do
if [[ "$command" = "Skype" ]]
then
echo $pid
break
fi
done
The while read is reading two variables. The trick is that read uses white space to divide the variables it reads in. However, since there are only two variables, the last one will contain the rest of the entire line. Thus if the command is Skype Controller, the entire command will be put into $command even though there's a space in it.
Now, we don't have to use a regular expression. We can compare the command with an equality.
This is longer to type in, but you're actually using fewer commands and less piping. Remember awk is looping through each line. All you're doing here is making it more explicit. In the end, this is actually much more efficient that what you originally had.
If pgrep is available on Mac, you can use pgrep '^Skype$'. This will list the process id of all processes called Skype.
You used the wrong quotes in your script:
ps -clx | grep "$1" | awk '{print $2}' | head -1
or
pgrep "^$1$"
The problem with your second example is that the $1 is in single quotes, which prevents bash from expanding the variable. There is already a utility that accomplishes what you want without manually parsing ps output.
pgrep "$1"
You can do this in AppleScript:
tell application "System Events"
set skypeProcess to the process "Skype"
set pid to the unix id of skypeProcess
pid
end tell
which means you can use 'osascript' to get the PID from within a shell script:
$ osascript -e "tell application \"System Events\"" -e "set skypeProcess to the process \"Skype\"" -e "set pid to the unix id of skypeProcess" -e "pid" -e "end tell"
3873
You can format the output of ps using the -o [field],... and list by process name using -C [command_name] ;however, ps will still print the column header, which can be removed by piping it through grep -v PID
ps -o pid -C "$1" |grep -v PID
where $1 would be the command name (in this case Skype)
I'd so something like:
ps aux | grep Skype | awk 'NR==1 {print $2}'
==== UPDATE ====
Use the parameter without quotes and use single quotes for awk
#!/bin/bash
ps aux | grep $1 | awk 'NR==1 {print $2}'
Method 1 - Use awk
I don't see any reason to use the -l flag (long format), I also don't see any reason to use grep and awk at the same time: awk has grep capability built in. Here is my plan: use ps and output just 2 columns: pid and command, then use awk to pick out what you want:
ps -cx -o pid,command | awk '$2 == "Skype" { print $1 }'
Method 2 - Use bash
This method has the advantage that if you already script in bash, you don't even need awk, which save one process. The solution is longer than the other method, but very straight forward.
#!/bin/bash
ps -cx -o pid,command | {
while read pid command
do
if [ "_$command" = "_$1" ]
then
# Do something with the pid
echo Found: pid=$pid, command=$command
break
fi
done
}
pgrep myAwesomeAppName
This works great under Catalina 10.15.2
Use double quotes to allow bash to perform variable substitution.
Single quotes disable bash variable substitution mechanism.
ps -clx | grep "$1" | awk "{print $2}" | head -1

Resources