Difference using ps|wc -l with or without bash variable - bash

Using bash, I would like to understand the different outputs between :
ps |wc -l
4
and
n=$(ps|wc -l)
echo $n
5
I guess the $(ps|wc -l) instruction is creating an additional subprocess, but I don't really understand why it is addded to the ps count

You said it: $( ) creates a subprocess. Because the command ps without options precisely lists all subprocesses of the current shell, you get one more line. I checked this by replacing wc with tee:
$ ps | tee four
PID TTY TIME CMD
XXXXXXX pts/5 00:00:00 bash
YYYYYYY pts/5 00:00:00 ps
ZZZZZZZ pts/5 00:00:00 tee
$ : $(ps -H | tee five)
$ cat five
PID TTY TIME CMD
XXXXXXX pts/5 00:00:00 bash
YYYYYYY pts/5 00:00:00 bash
ZZZZZZZ pts/5 00:00:00 ps
ΩΩΩΩΩΩΩ pts/5 00:00:00 tee
I also passed -H to ps so that it evidences the process tree.

Related

Executing a shell with a command and returning

man bash seems to suggest that if I want to execute a command in a separate bash shell all I have to do is bash -c command:
-c string If the -c option is present, then commands are read from string.
I want to do that because I need to run a few things in different environments:
bash --rcfile ~/.bashrc.a -c mytest.a
bash --rcfile ~/.bashrc.b -c mytest.b
However, that didn't work as expected; one can see that by the number of bash shells running, for example:
$ bash
$ ps
PID TTY TIME CMD
7554 pts/0 00:00:00 bash
7573 pts/0 00:00:00 ps
28616 pts/0 00:00:00 bash
$ exit
exit
$ ps
PID TTY TIME CMD
7582 pts/0 00:00:00 ps
28616 pts/0 00:00:00 bash
$ bash -c ps
PID TTY TIME CMD
7583 pts/0 00:00:00 ps
28616 pts/0 00:00:00 bash
How should the invocation of bash should be modified so that it would start a new shell with the specified rc, execute the given command in that shell (with the env modified according to the rc), and exit back?
It's already working exactly the way you want it to. The lack of an extra process is simply due to bash's tail-call optimization.
Bash recognizes that there's no point in having a shell instance whose only job is to wait for a process and exit. It will instead skip the fork and exec the process directly. This is a huge win for e.g. var=$(ps), where it cuts the number of expensive forks from 2 to 1.
If you give it additional commands to run afterwards, this optimization is no longer valid, and then you'll see the additional process:
$ bash -c 'ps'
PID TTY TIME CMD
4531 pts/10 00:00:00 bash
4540 pts/10 00:00:00 ps
$ bash -c 'ps; exit $?'
PID TTY TIME CMD
4531 pts/10 00:00:00 bash
4549 pts/10 00:00:00 bash
4550 pts/10 00:00:00 ps
bash --rcfile ~/.bashrc.a mytest.a will already run mytest.a in a separate process. -c is for specifying a shell command directly, rather than running a script.
# NO!
bash for x in 1 2 3; do echo "$x"; done
# Yes.
bash -c 'for x in 1 2 3; do echo "$x"; done'

How to get the two lines(head+filtered content) as output with a simple command?

I want to get the head as in my output.
ps lax |head -n 1
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
And the filtered line:
ps lax |grep openbox |grep -v grep
0 1000 1608 1513 20 0 206408 20580 SyS_po S ? 0:00 openbox --config-file /home/debian9/.config/openbox/lxde-rc.xml
What i expect to get is as below two lines:
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 1000 1608 1513 20 0 206408 20580 SyS_po S ? 0:00 openbox --config-file /home/debian9/.config/openbox/lxde-rc.xml
How to get the two lines(head+filtered content) as output with a simple command?
For compound conditions you use awk, not grep:
ps lax | awk 'NR==1 || /[o]penbox/'
Note the idiomatic use of [o] in #Cyrus and my answers so that the regexp doesn't match on this command itself so you don't need to explicitly remove this command name from with regexp.
ps lax | grep -e '^F' -e '[o]penbox'
or
ps lax | grep '^F\|[o]penbox'
Ed's answer using awk plus the NR==1 condition is the best solution.
For completeness, let me show the use of tee and processs substitution with >(command).
For instance: to display current processes (with ps) other than bash while retaining the ps header line, use tee in the following way:
$ ps | tee >(sed -n 1p) >(sed 1d | grep -v bash) > /dev/null
PID PPID PGID WINPID TTY UID STIME COMMAND
5782 2514 3792 1940 cons2 1415878 12:21:38 /usr/bin/ps
9998 2 9708 9708 ? 1415878 12:38:41 /usr/bin/ssh-agent
$
Here tee redirects the output to two processes:
one to display the first line (first sed -n 1p),
then other that filters the first line (the other sed 1d) and does an additional filtering with grep.
Finally, to prevent tee from dumping the original ps output, stdout is redirected to /dev/null

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

Retrieving full command line (w/ pipes &c) from a running bash script

How can I get the complete line of code running in the bash in a script that is run from within this line?
ping -c 2 google.com & ping -c 2 aol.com | grep aol & sh myscript.sh
where I want to retrieve the complete upper line in myscript.sh somehow.
My current approach is:
ping -c 2 google.com & ping -c 2 aol.com | grep aol & ps -ef --sort=start_time
And then correlate the PPID and the start time of the process to get what was run.
UID PID PPID C STIME TTY TIME CMD
nm+ 2881 6599 0 12:09 pts/1 00:00:00 ping -c 2 google.com
nm+ 2882 6599 0 12:09 pts/1 00:00:00 ping -c 2 aol.com
nm+ 2883 6599 0 12:09 pts/1 00:00:00 grep --color=auto abc
nm+ 2884 6599 0 12:09 pts/1 00:00:00 ps -ef --sort=start_time
I dont like it since I am unable to say how the processes are connected (pipes or just parallel execution) and therefore its impossible to reconstruct the exact line that was run in the bash. Also it feels to hackish for the right way.
You can grep "pipe" from lsof and find the correlated commands from the pipe id for a process and find the process id and look for details for the correlated processes.
Assuming bash 4.0 or newer:
#!/usr/bin/env bash
exec 3>"$1"; shift
BASH_XTRACEFD=3 PS4=':$BASH_SOURCE:$LINENO:+'
set -x
source "$#"
...if saved as bash_trace, used as:
bash_trace logfile scriptname arg1 arg2 ...
...then, to look up the actual line number, one can use something like the following:
IFS=: read -r filename lineno _ < <(tail -n 1 logfile)
sed -e "${lineno}q;d" <"$filename"

Why does echo $$ return a number? [duplicate]

This question already has answers here:
What are the special dollar sign shell variables?
(4 answers)
Closed 8 years ago.
Why do I get a number when doing that :
echo $$
which returns
489
If I open a new terminal it returns another number. It seems it's related to the pid of the terminal session, but why ?
$$ means your current PID.
As seen in Bash Reference Manual - 3.4.2 Special Parameters:
$
Expands to the process ID of the shell. In a () subshell, it expands
to the process ID of the invoking shell, not the subshell.
You can test it by doing ps -ef | grep 489, and it will show the process in which you are logged in.
For example in my case:
$ echo $$
3470
$ ps -ef | grep 3470
1000 3470 3469 0 10:59 pts/3 00:00:00 -bash <---- this process
1000 8151 3470 0 15:37 pts/3 00:00:00 ps -ef
1000 8152 3470 0 15:37 pts/3 00:00:00 grep --color=auto 3470
Because that's how it is defined. $$ is a special shell variable (like e.g. $!, $_, $#, $1, ...) referring to the PID of the invoked shell.
You will find an excellent explanation in this post.
$$ pid of the current shell (not subshell)

Resources