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

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"

Related

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

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.

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 many child process (subprocess) generated by 'su -c command'

When using Upstart, controlling subprocesses (child process) is quite important. But what confused me is as following, which has gone beyond upstart itself:
scenario 1:
root#ubuntu-jstorm:~/Desktop# su cr -c 'sleep 20 > /tmp/a.out'
I got 3 processes by: cr#ubuntu-jstorm:~$ ps -ef | grep -v grep | grep sleep
root 8026 6544 0 11:11 pts/2 00:00:00 su cr -c sleep 20 > /tmp/a.out
cr 8027 8026 0 11:11 ? 00:00:00 bash -c sleep 20 > /tmp/a.out
cr 8028 8027 0 11:11 ? 00:00:00 sleep 20
scenario 2:
root#ubuntu-jstorm:~/Desktop# su cr -c 'sleep 20'
I got 2 processes by: cr#ubuntu-jstorm:~$ ps -ef | grep -v grep | grep sleep
root 7975 6544 0 10:03 pts/2 00:00:00 su cr -c sleep 20
cr 7976 7975 0 10:03 ? 00:00:00 sleep 20
The process of sleep 20 is the one I care, especially in Upstart, the process managed by Upstart should be this while not bash -c sleep 20 > /tmp/a.out is managed by Upstart, while not the sleep 20.
In scenario 1, upstart will not work correctly, above is the reason.
Therefore, why scenario 1 got 3 process, this doesn't make sense for me. Even though I know I can use command 'exec' to fix it, I just want to get the procedure what happened when the two command committed.
su -c starts the shell and passes it the command via its -c option. The shell may spawn as many processes as it likes (it depends on the given command).
It appears the shell executes the command directly without forking in some cases e.g., if you run su -c '/bin/sleep $$' then the apparent behaviour as if:
su starts a shell process (e.g., /bin/sh)
the shell gets its own process id (PID) and substitute $$ with it
the shell exec() /bin/sleep.
You should see in ps output that sleep's argument is equal to its pid in this case.
If you run su -c '/bin/sleep $$ >/tmp/sleep' then /bin/sleep argument is different from its PID (it is equal to the ancestor's PID) i.e.:
su starts a shell process (e.g., /bin/sh)
the shell gets its own process id (PID) and substitute $$ with it
the shell double forks and exec() /bin/sleep.
The double fork indicates that the actual sequence of events might be different e.g., su could orchestrate the forking or not forking, not the shell (I don't know). It seems the double fork is there to make sure that the command won't get a controlling terminal.
command > file
This is not atomic action, and actually done in 2 process.
One is execute the command;
the other do the output redirection.
Above two action can not done in one process.
Am I right?

Get the username and the process ID of a process in bash

I am writing a bash script where I need to find out the userID of a process. For an example let the process be bash itself.
I tried ps aux | grep ba[s]h but the following was returned:
1000 2745 0.0 0.1 28360 5440 pts/1 Ss 10:11 0:01 bash
I see the userID 1000 displayed, but I want the username.
This can happen if the username is longer than 8 characters (OR) id has no name. But, If you want the username in the ps output then try this,
ps -eo uname:20,pid,pcpu,pmem,sz,tty,stat,time,cmd | grep '[b]ash'
You can parse out the /proc entry if you are on Linux and if you just need a numeric pid (or are OK with it). Here is an example for the mysqld process:
grep -e '^Uid:' /proc/$(pidof mysqld)/status | cut -f 2
The shortest way I found so far ( $PID - ID of the process inspected):
ps -p $PID -o euid=
Here is an example for gedit process
grep -w Pid /proc/$(pidof gedit)/status | cut -f 2

Can't understand ps | wc output differences

I was trying to write a set of functions that could check to see if a process name was running when I encountered some unexpected output. I've condensed the issue in the following script names isRunning.sh which depends on a system ps command that can take the '-fC' arguments...
#!/bin/bash
progname=isRunning.sh
ps -fC isRunning.sh
pRet=`ps -fC ${progname} | wc -l`
echo pRet $pRet
psOut=`ps -fC ${progname}`
wcOut=`echo "${psOut}" | wc -l`
echo
echo ps output
echo "${psOut}"
echo
echo wcOut $wcOut
The first attempt at piping the ps output to wc gets a return of 3. The second attempt gets the expected return value of 2. Can anyone explain this behavior? I figure it's got to be something stupid I am overlooking.
Thanks,
bbb
edit: my output
UID PID PPID C STIME TTY TIME CMD
root 6717 5940 0 13:10 pts/0 00:00:00 /bin/bash ./isRunning.sh
pRet 3
ps output
UID PID PPID C STIME TTY TIME CMD
root 6717 5940 0 13:10 pts/0 00:00:00 /bin/bash ./isRunning.sh
wcOut 2
I get 2 both attempts. Your ps might be outputting an extra blank line, or somesuch, and then your shell's backtick expansion stripping it. Or maybe you actually had two processes matching the first time you ran it.
If you just want to see if its running, check the exit code from your ps:
if ps -C "${progname}" > /dev/null; then
echo its running
else
echo not running
fi
Even better, you should take a look at pidof and pgrep if you can rely on them being present on whichever systems you're targeting. Or use the LSB functions, if you're on Linux.
edit: Actually, since you're looking for copies of yourself running, you might be picking up the shell doing a fork to implement the pipe.

Resources