Selectively switch bash script on and off from inside another bash script - linux

I have a Raspberry Pi running a bash script, eg, ScriptA, that calls a web function running on a cloud server and the function returns a value to the calling script. The cloud server contains a system where, visitors to the site are given a few options using buttons, eg, Button1, Button2 and Button3. The option selected by the visitor is sent by the web function to the calling bash script on the Pi as Request1, Request2 or Request3 for Button1, Button and Button3 respectively. The ScriptA keeps calling the web function recursively to check for any change in Request value. The value returned by the function to the Pi is then used to run another script on the Pi, eg, Request1 runs Script1, Request2 runs Script2 and Request3 runs Script3. Now, the webpage is dynamic and the visitor can click different options, one after the other, and the script running on the Pi also needs to change accordingly.
Button1 => Request1 |                           | Request1 => Script1
Button2 => Request2 | Cloud Server <=> Pi(ScriptA) | Request2 => Script2
Button3 => Request3 |                           | Request3 => Script3
What I have done is, I have used a switch case in the ScriptA to switch between the different scripts 1, 2 and 3. What I want is, as soon as the visitor to the site chooses a different option and the Pi receives the new request, the script for the previously selected option stops executing, and the script for the new request starts executing.
#!/bin/bash
# call.sh
while:
do
req=$(curl -d "param=$param" http://www.example.net/req.php)
case $req in
*req1*)
sudo sh /home/pi/stopscript2.sh
sudo sh /home/pi/stopscript3.sh
sudo sh /home/pi/startscript1.sh
;;
*req2*)
sudo sh /home/pi/stopscript1.sh
sudo sh /home/pi/stopscript3.sh
sudo sh /home/pi/startscript2.sh
;;
*req3*)
sudo sh /home/pi/stopscript1.sh
sudo sh /home/pi/stopscript2.sh
sudo sh /home/pi/startscript3.sh
;;
esac
done
But, the problem with this piece of code is that, the first time the pi receives a request and starts executing the specific script for the incoming request, no more calls to the web function are made thereafter and the script continues executing the first request, regardless of whether the user has selected a new option or not.
How do I make it work as desired? I hope I could frame my question well enough.
Help is much welcome. Thanks in advance.

Assuming that:
we keep reading the new input and just execute the freshly gathered
value, whether "Request1", "Reqest2" or "Request3" and get rid of
the old one.
You don't mind previous process to be killed rather than to be stopped.
In addition to your approach, I have added the following:
A date,time print function, to demonstrate operation timing clearly. This can be omitted later one once functionality has been verified.
A 'getpid' function, which freshly calculates the pid of the previous running script and passes on to the kill function
To demonstrate this, I will be tailing a file data.txt which is continuously growing as 3 backgroud echo commands are continuously putting Request 1,2,3 into it. This will serve as our somewhat dynamic random input. A sample below.
Sample:
%_STATION#gaurav * /root/ga/shell> tail -f data.txt
Request2
Request1
Request1
Request1
Request3
Request2
Request1
The script output:
The line with EMPTY represents when no PID was found. EMPTY
This indicates a new request read from your file: [17-02-17 20:38:17|[>>>>NEW>>>REQUEST>> [Request3]>>>>]
[gaurav 82577 1 0 20:38 pts/3 00:00:00 sleep 11]. This is the old running process, so I will kill it now.
Its PID is captured: [OLDPID---->82577]
Once killed, a new one fired, and it is "Request3": [gaurav 82611 1 0 20:38 pts/3 00:00:00 sleep 13]
Now this keeps repeating. See the log below for more clarity.
A log excerpt:
17-02-17 20:38:16|EMPTY
17-02-17 20:38:16|[***NEWPROCESS***[82504]*****]
17-02-17 20:38:16|[>>>>NEW>>>REQUEST>> [Request1]>>>>]
17-02-17 20:38:16|gaurav 82504 1 0 20:38 pts/3 00:00:00 sleep 12
17-02-17 20:38:16|[OLDPID---->82504]
17-02-17 20:38:16|gaurav 82543 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:16|[***NEWPROCESS***[82543]*****]
17-02-17 20:38:16|[>>>>NEW>>>REQUEST>> [Request1]>>>>]
17-02-17 20:38:16|gaurav 82543 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:16|[OLDPID---->82543]
17-02-17 20:38:17|gaurav 82577 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:17|[***NEWPROCESS***[82577]*****]
17-02-17 20:38:17|[>>>>NEW>>>REQUEST>> [Request3]>>>>]
17-02-17 20:38:17|gaurav 82577 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:17|[OLDPID---->82577]
17-02-17 20:38:17|gaurav 82611 1 0 20:38 pts/3 00:00:00 sleep 13
17-02-17 20:38:17|[***NEWPROCESS***[82611]*****]
17-02-17 20:38:17|[>>>>NEW>>>REQUEST>> [Request2]>>>>]
17-02-17 20:38:17|gaurav 82611 1 0 20:38 pts/3 00:00:00 sleep 13
17-02-17 20:38:17|[OLDPID---->82611]
17-02-17 20:38:17|EMPTY
17-02-17 20:38:17|[***NEWPROCESS***[82664]*****]
17-02-17 20:38:17|[>>>>NEW>>>REQUEST>> [Request1]>>>>]
17-02-17 20:38:17|gaurav 82664 1 0 20:38 pts/3 00:00:00 sleep 12
17-02-17 20:38:17|[OLDPID---->82664]
17-02-17 20:38:17|gaurav 82699 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:17|[***NEWPROCESS***[82699]*****]
17-02-17 20:38:17|[>>>>NEW>>>REQUEST>> [Request1]>>>>]
17-02-17 20:38:17|gaurav 82699 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:17|[OLDPID---->82699]
17-02-17 20:38:18|gaurav 82733 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:18|[***NEWPROCESS***[82733]*****]
17-02-17 20:38:18|[>>>>NEW>>>REQUEST>> [Request3]>>>>]
17-02-17 20:38:18|gaurav 82733 1 0 20:38 pts/3 00:00:00 sleep 11
17-02-17 20:38:18|[OLDPID---->82733]
17-02-17 20:38:18|gaurav 82767 1 0 20:38 pts/3 00:00:00 sleep 13
17-02-17 20:38:18|[***NEWPROCESS***[82767]*****]
17-02-17 20:38:18|[>>>>NEW>>>REQUEST>> [Request2]>>>>]
As seen in the above log, the script is now able to act according to the incoming requests. In your case, you can replace the tail -f data.txt | while read line line portion with while true; do and then put your curl command.
The script:
%_STATION#gaurav * /root/ga/shell> cat request_action.sh
#!/bin/bash
# configure your scirpts. Here I used Sleeps.
s1='sleep 11' ; s2='sleep 12' ; s3='sleep 13'
# DateTime and print function.
prnt()
{
str="$1"
dt=$(date +"%d-%m-%y "%T)
[[ -z "$str" ]] && str="EMPTY"
echo "$dt|$str"
}
# Function to generate PID of previous running script.
getpid()
{
pid=$(ps -ef|egrep "$s1|$s2|$s3"|grep -v grep|awk '{print $2}')
echo "$pid"
}
# To kill the existing running script/s.
stopnstart()
{
prnt "$(ps -ef|egrep "$s1|$s2|$s3"|grep -v grep)"
prnt "[OLDPID---->$(getpid)]"
$(kill -9 $(getpid) 1>/dev/null 2>&1) # Kill the old PID.
$($1 1>/dev/null 2>&1 &) # Now run the fresh script.
prnt "$(ps -ef|egrep "$s1|$s1|$s3"|grep -v grep)"
prnt "[***NEWPROCESS***[$(getpid)]*****]"
}
# Main loop
tail -f data.txt | while read line
do
prnt "[>>>>NEW>>>REQUEST>> [$line]>>>>]"
case $line in
Request1)
stopnstart "$s1"
;;
Request2)
stopnstart "$s2"
;;
Request3)
stopnstart "$s3"
;;
esac
done
%_STATION#gaurav * /root/ga/shell>
I hope I got it right. Thanks.

Related

Run command in golang and detach it from process

Problem:
I'm writing program in golang on linux that needs to execute long running process so that:
I redirect stdout of running process to file.
I control the user of process.
Process doesn't die when my program exits.
The process doesn't become a zombie when it crashes.
I get PID of running process.
I'm running my program with root permissions.
Attempted solution:
func Run(pathToBin string, args []string, uid uint32, stdLogFile *os.File) (int, error) {
cmd := exec.Command(pathToBin, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{
Uid: uid,
},
}
cmd.Stdout = stdLogFile
if err := cmd.Start(); err != nil {
return -1, err
}
go func() {
cmd.Wait() //Wait is necessary so cmd doesn't become a zombie
}()
return cmd.Process.Pid, nil
}
This solution seems to satisfy almost all of my requirements except that when I send SIGTERM/SIGKILL to my program the underlying process crashes. In fact I want my background process to be as separate as possible: it has different parent pid, group pid etc. from my program. I want to run it as daemon.
Other solutions on stackoverflow suggested to use cmd.Process.Release() for similar use cases, but it doesn't seem to work.
Solutions which are not applicable in my case:
I have no control over code of process I'm running. My solution has to work for any process.
I can't use external commands to run it, just pure go. So using systemd or something similar is not applicable.
I can in fact use library that is easily importable using import from github etc.
TLDR;
Just use https://github.com/hashicorp/go-reap
There is a great Russian expression which reads "don't try to give birth to a bicycle" and it means don't reinvent the wheel and keep it simple. I think it applies here. If I were you, I'd reconsider using one of:
https://github.com/krallin/tini
https://busybox.net/
https://software.clapper.org/daemonize/
https://wiki.gentoo.org/wiki/OpenRC
https://www.freedesktop.org/wiki/Software/systemd/
This issue has already been solved ;)
Your question is imprecise or you are asking for non-standard features.
In fact I want my background process to be as separate as possible: it has different parent pid, group pid etc. from my program. I want to run it as daemon.
That is not how process inheritance works. You can not have process A start Process B and somehow change the parent of B to C. To the best of my knowledge this is not possible in Linux.
In other words, if process A (pid 55) starts process B (100), then B must have parent pid 55.
The only way to avoid that is have something else start the B process such as atd, crond, or something else - which is not what you are asking for.
If parent 55 dies, then PID 1 will be the parent of 100, not some arbitrary process.
Your statement "it has different parent pid" does not makes sense.
I want to run it as daemon.
That's excellent. However, in a GNU / Linux system, all daemons have a parent pid and those parents have a parent pid going all the way up to pid 1, strictly according to the parent -> child rule.
when I send SIGTERM/SIGKILL to my program the underlying process crashes.
I can not reproduce that behavior. See case8 and case7 from the proof-of-concept repo.
make case8
export NOSIGN=1; make build case7
unset NOSIGN; make build case7
$ make case8
{ sleep 6 && killall -s KILL zignal; } &
./bin/ctrl-c &
sleep 2; killall -s TERM ctrl-c
kill with:
{ pidof ctrl-c; pidof signal ; } | xargs -r -t kill -9
main() 2476074
bashed 2476083 (2476081)
bashed 2476084 (2476081)
bashed 2476085 (2476081)
zignal 2476088 (2476090)
go main() got 23 urgent I/O condition
go main() got 23 urgent I/O condition
zignal 2476098 (2476097)
go main() got 23 urgent I/O condition
zignal 2476108 (2476099)
main() wait...
p 2476088
p 2476098
p 2476108
p 2476088
go main() got 15 terminated
sleep 1; killall -s TERM ctrl-c
p 2476098
p 2476108
p 2476088
go main() got 15 terminated
sleep 1; killall -s TERM ctrl-c
p 2476098
p 2476108
p 2476088
Bash c 2476085 EXITs ELAPSED 4
go main() got 17 child exited
go main() got 23 urgent I/O condition
main() children done: 1 %!s(<nil>)
main() wait...
go main() got 15 terminated
go main() got 23 urgent I/O condition
sleep 1; killall -s KILL ctrl-c
p 2476098
p 2476108
p 2476088
balmora: ~/src/my/go/doodles/sub-process [main]
$ p 2476098
p 2476108
Bash _ 2476083 EXITs ELAPSED 6
Bash q 2476084 EXITs ELAPSED 8
The bash processes keep running after the parent is killed.
killall -s KILL ctrl-c;
All 3 "zignal" sub-processes are running until killed by
killall -s KILL zignal;
In both cases the sub-processes continue to run despite main process being signaled with TERM, HUP, INT. This behavior is different in a shell environment because of convenience reasons. See the related questions about signals. This particular answer illustrates a key difference for SIGINT. Note that SIGSTOP and SIGKILL cannot be caught by an application.
It was necessary to clarify the above before proceeding with the other parts of the question.
So far you have already solved the following:
redirect stdout of sub-process to a file
set owner UID of sub-process
sub-process survives death of parent (my program exits)
the PID of sub-process can be seen by the main program
The next one depends on whether the children are "attached" to a shell or not
sub-process survives the parent being killed
The last one is hard to reproduce, but I have heard about this problem in the docker world, so the rest of this answer is focused on addressing this issue.
sub-process survives if the parent crashes and does not become a zombie
As you have noted, the Cmd.Wait() is necessary to avoid creating zombies. After some experimentation I was able to consistency produce zombies in a docker environment using an intentionally simple replacement for /bin/sh. This "shell" implemented in go will only run a single command and not much else in terms of reaping children. You can study the code over at github.
The zombie solution
the simple wrapper which causes zombies
package main
func main() {
Sh()
}
The reaper wrapper
package main
import (
"fmt"
"sync"
"github.com/fatih/color"
"github.com/hashicorp/go-reap"
)
func main() {
if reap.IsSupported() {
done := make(chan struct{})
var reapLock sync.RWMutex
pids := make(reap.PidCh, 1)
errors := make(reap.ErrorCh, 1)
go reap.ReapChildren(pids, errors, done, &reapLock)
go report(pids, errors, done)
Sh()
close(done)
} else {
fmt.Println("Sorry, go-reap isn't supported on your platform.")
}
}
func report(pids reap.PidCh, errors reap.ErrorCh, done chan struct{}) {
sprintf := color.New(color.FgWhite, color.Bold).SprintfFunc()
for ;; {
select {
case pid := <-pids:
println(sprintf("raeper pid %d", pid))
case err := <-errors:
println(sprintf("raeper er %s", err))
case <-done:
return
}
}
}
The init / sh (pid 1) process which runs other commands
package main
import (
"os"
"os/exec"
"strings"
"time"
"github.com/google/shlex"
"github.com/tox2ik/go-poc-reaper/fn"
)
func Sh() {
args := os.Args[1:]
script := args[0:0]
if len(args) >= 1 {
if args[0] == "-c" {
script = args[1:]
}
}
if len(script) == 0 {
fn.CyanBold("cmd: expecting sh -c 'foobar'")
os.Exit(111)
}
var cmd *exec.Cmd
parts, _ := shlex.Split(strings.Join(script, " "))
if len(parts) >= 2 {
cmd = fn.Merge(exec.Command(parts[0], parts[1:]...), nil)
}
if len(parts) == 1 {
cmd = fn.Merge(exec.Command(parts[0]), nil)
}
if fn.IfEnv("HANG") {
fn.CyanBold("cmd: %v\n start", parts)
ex := cmd.Start()
if ex != nil {
fn.CyanBold("cmd %v err: %s", parts, ex)
}
go func() {
time.Sleep(time.Millisecond * 100)
errw := cmd.Wait()
if errw != nil {
fn.CyanBold("cmd %v err: %s", parts, errw)
} else {
fn.CyanBold("cmd %v all done.", parts)
}
}()
fn.CyanBold("cmd: %v\n dispatched, hanging forever (i.e. to keep docker running)", parts)
for {
time.Sleep(time.Millisecond * time.Duration(fn.EnvInt("HANG", 2888)))
fn.SystemCyan("/bin/ps", "-e", "-o", "stat,comm,user,etime,pid,ppid")
}
} else {
if fn.IfEnv("NOWAIT") {
ex := cmd.Start()
if ex != nil {
fn.CyanBold("cmd %v start err: %s", parts, ex)
}
} else {
ex := cmd.Run()
if ex != nil {
fn.CyanBold("cmd %v run err: %s", parts, ex)
}
}
fn.CyanBold("cmd %v\n dispatched, exit docker.", parts)
}
}
The Dockerfile
FROM scratch
# for sh.go
ENV HANG ""
# for sub-process.go
ENV ABORT ""
ENV CRASH ""
ENV KILL ""
# for ctrl-c.go, signal.go
ENV NOSIGN ""
COPY bin/sh /bin/sh ## <---- wrapped or simple /bin/sh or "init"
COPY bin/sub-process /bin/sub-process
COPY bin/zleep /bin/zleep
COPY bin/fork-if /bin/fork-if
COPY --from=busybox:latest /bin/find /bin/find
COPY --from=busybox:latest /bin/ls /bin/ls
COPY --from=busybox:latest /bin/ps /bin/ps
COPY --from=busybox:latest /bin/killall /bin/killall
Remaining code / setup can be seen here:
https://github.com/tox2ik/go-poc-reaper
Case 5 (simple /bin/sh)
The gist of it is we start two sub-processes from go, using the "parent" sub-process binary. The first child is zleep and the second fork-if. The second one starts a "daemon" that runs a forever-loop in addition to a few short-lived threads. After a while, we kill the sub-procss parent, forcing sh to take over the parenting for these children.
Since this simple implementation of sh does not know how to deal with abandoned children, the children become zombies.
This is standard behavior. To avoid this, init systems are usually responsible for cleaning up any such children.
Check out this repo and run the cases:
$ make prep build
$ make prep build2
The first one will use the simple /bin/sh in the docker container, and the socond one will use the same code wrapped in a reaper.
With zombies:
$ make prep build case5
(…)
main() Daemon away! 16 (/bin/zleep)
main() Daemon away! 22 (/bin/fork-if)
(…)
main() CRASH imminent
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x49e45c]
goroutine 1 [running]:
main.main()
/home/jaroslav/src/my/go/doodles/sub-process/sub-process.go:137 +0xfc
cmd [/bin/sub-process /log/case5 3 /bin/zleep 111 2 -- /dev/stderr 3 /bin/fork-if --] err: exit status 2
Child '1' done
thread done
STAT COMMAND USER ELAPSED PID PPID
R sh 0 0:02 1 0
S zleep 3 0:02 16 1
Z fork-if 3 0:02 22 1
R fork-child-A 3 0:02 25 1
R fork-child-B 3 0:02 26 25
S fork-child-C 3 0:02 27 26
S fork-daemon 3 0:02 28 27
R ps 0 0:01 30 1
Child '2' done
thread done
daemon
(…)
STAT COMMAND USER ELAPSED PID PPID
R sh 0 0:04 1 0
Z zleep 3 0:04 16 1
Z fork-if 3 0:04 22 1
Z fork-child-A 3 0:04 25 1
R fork-child-B 3 0:04 26 1
S fork-child-C 3 0:04 27 26
S fork-daemon 3 0:04 28 27
R ps 0 0:01 33 1
(…)
With reaper:
$ make -C ~/src/my/go/doodles/sub-process case5
(…)
main() CRASH imminent
(…)
Child '1' done
thread done
raeper pid 24
STAT COMMAND USER ELAPSED PID PPID
S sh 0 0:02 1 0
S zleep 3 0:01 18 1
R fork-child-A 3 0:01 27 1
R fork-child-B 3 0:01 28 27
S fork-child-C 3 0:01 30 28
S fork-daemon 3 0:01 31 30
R ps 0 0:01 32 1
Child '2' done
thread done
raeper pid 27
daemon
STAT COMMAND USER ELAPSED PID PPID
S sh 0 0:03 1 0
S zleep 3 0:02 18 1
R fork-child-B 3 0:02 28 1
S fork-child-C 3 0:02 30 28
S fork-daemon 3 0:02 31 30
R ps 0 0:01 33 1
STAT COMMAND USER ELAPSED PID PPID
S sh 0 0:03 1 0
S zleep 3 0:02 18 1
R fork-child-B 3 0:02 28 1
S fork-child-C 3 0:02 30 28
S fork-daemon 3 0:02 31 30
R ps 0 0:01 34 1
raeper pid 18
daemon
STAT COMMAND USER ELAPSED PID PPID
S sh 0 0:04 1 0
R fork-child-B 3 0:03 28 1
S fork-child-C 3 0:03 30 28
S fork-daemon 3 0:03 31 30
R ps 0 0:01 35 1
(…)
Here is a picture of the same output, which may be less confusing to read.
Zombies
Reaper
How to run the cases in the poc repo
Get the code
git clone https://github.com/tox2ik/go-poc-reaper.git
One terminal:
make tail-cases
Another terminal
make prep
make build
or make build2
make case0 case1
...
Related questions:
go
How to create a daemon process in Golang?
How to start a Go program as a daemon in Ubuntu?
how to keep subprocess running after program exit in golang?
Prevent Ctrl+C from interrupting exec.Command in Golang
signals
https://unix.stackexchange.com/questions/386999/what-terminal-related-signals-are-sent-to-the-child-processes-of-the-shell-direc
https://unix.stackexchange.com/questions/6332/what-causes-various-signals-to-be-sent
https://en.wikipedia.org/wiki/Signal_(IPC)#List_of_signals
Related discussions:
https://github.com/golang/go/issues/227
https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
Relevant projects:
http://software.clapper.org/daemonize/ (what I would use)
https://github.com/hashicorp/go-reap (if you must have run go on pid 1)
https://github.com/sevlyar/go-daemon (mimics posix fork)
Relevant prose:
A zombie process is a process whose execution is completed but it still has an entry in the process table. Zombie processes usually occur for child processes, as the parent process still needs to read its child’s exit status. Once this is done using the wait system call, the zombie process is eliminated from the process table. This is known as reaping the zombie process.
from https://www.tutorialspoint.com/what-is-zombie-process-in-linux

Self duplicate background script

This is a background script test.
When run it launch two processes and I don't understand why.
One stop after sleep 20. And other forgets.
#!/bin/bash
back(){
n=0
while [ 1 ]
do
echo $n
n=$(($n+1))
sleep 5
done
}
back &
sleep 20
exit
command "ps -a" in call:
PID TTY TIME CMD
8964 pts/2 00:00:00 backgroundtest
8965 pts/2 00:00:00 backgroundtest
8966 pts/2 00:00:00 sleep
8982 pts/2 00:00:00 sleep
after sleep 20:
PID TTY TIME CMD
8965 pts/2 00:00:00 backgroundtest
9268 pts/2 00:00:00 sleep
then run forever...
why?
while [ 1 ] is an infinite loop. [ 1 ] is always true.
So back & is an infinite loop, started in background (&), then execution continues with sleep 20, which does end after 20 seconds, leaving you with two processes for 20 seconds (& starts a new process in background), then the infinite one after that.

How to narrow down the process listing output below UNIX/LINUX

FROM:
noaccess 7491 6817 0 Jul 19 ? 281:19 /usr/java/bin/java -
server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4
oracle 1715 11577 0 Nov 23 ? 32:33
/appl/oracle/product/10.2.0/db_1/jdk/bin/java -server -Xmx256M -
XX:MaxPermSize=
virtuo 21844 21814 0 17:27:27 pts/3 0:00 grep java
TO:
noaccess 7491 6817 0 Jul 19 ? 281:19 /usr/java/bin/java -
server -Xmx128m -XX:+UseParallelGC -XX:ParallelGCThreads=4
oracle 1715 11577 0 Nov 23 ? 32:33
/appl/oracle/product/10.2.0/db_1/jdk/bin/java -server -Xmx256M -
XX:MaxPermSize=
I need to remove virtuo 21844 21814 0 17:27:27 pts/3 0:00 grep java line
I am really new to UNIX/LINUX command.
Assuming that your FROM output was generated from an 'X' command, so:
X | sed '$d'
That will remove the last line of the output. To remove a specific line, you could use something like:
X | sed '/regex/d'
That will remove every line that has a match on the passed regex.

time command output on an already running process

I have a process that spawns some other processes,
I want to use the time command on a specific process and get the same output as the time command.
Is that possible and how?
I want to use the time command on a specific process and get the same output as the time command.
Probably it is enough just to use pidstat to get user and sys time:
$ pidstat -p 30122 1 4
Linux 2.6.32-431.el6.x86_64 (hostname) 05/15/2014 _x86_64_ (8 CPU)
04:42:28 PM PID %usr %system %guest %CPU CPU Command
04:42:29 PM 30122 706.00 16.00 0.00 722.00 3 has_serverd
04:42:30 PM 30122 714.00 12.00 0.00 726.00 3 has_serverd
04:42:31 PM 30122 714.00 14.00 0.00 728.00 3 has_serverd
04:42:32 PM 30122 708.00 16.00 0.00 724.00 3 has_serverd
Average: 30122 710.50 14.50 0.00 725.00 - has_serverd
If not then according to strace time uses wait4 system call (http://linux.die.net/man/2/wait4) to get information about a process from the kernel. The same info returns getrusage but you cannot call it for an arbitrary process according to its documentation (http://linux.die.net/man/2/getrusage).
So, I do not know any command that will give the same output. However it is feasible to create a bash script that gets PID of the specific process and outputs something like time outpus then
This script does these steps:
1) Get the number of clock ticks per second
getconf CLK_TCK
I assume it is 100 and 1 tick is equal to 10 milliseconds.
2) Then in loop do the same sequence of commands while exists the directory /proc/YOUR-PID:
while [ -e "/proc/YOUR-PID" ];
do
read USER_TIME SYS_TIME REAL_TIME <<< $(cat /proc/PID/stat | awk '{print $14, $15, $22;}')
sleep 0.1
end loop
Some explanation - according to man proc :
user time: ($14) - utime - Amount of time that this process has been scheduled in user mode, measured in clock ticks
sys time: ($15) - stime - Amount of time that this process has been scheduled in kernel mode, measured in clock ticks
starttime ($22) - The time in jiffies the process started after system boot.
3) When the process is finished get finish time
read FINISH_TIME <<< $(cat '/proc/self/stat' | awk '{print $22;}')
And then output:
the real time = ($FINISH_TIME-$REAL_TIME) * 10 - in milliseconds
user time: ($USER_TIME/(getconf CLK_TCK)) * 10 - in milliseconds
sys time: ($SYS_TIME/(getconf CLK_TCK)) * 10 - in milliseconds
I think it should give roughly the same result as time. One possible problem I see is if the process exists for a very short period of time.
This is my implementation of time:
#!/bin/bash
# Uses herestrings
print_res_jeffies()
{
let "TIME_M=$2/60000"
let "TIME_S=($2-$TIME_M*60000)/1000"
let "TIME_MS=$2-$TIME_M*60000-$TIME_S*1000"
printf "%s\t%dm%d.%03dms\n" $1 $TIME_M $TIME_S $TIME_MS
}
print_res_ticks()
{
let "TIME_M=$2/6000"
let "TIME_S=($2-$TIME_M*6000)/100"
let "TIME_MS=($2-$TIME_M*6000-$TIME_S*100)*10"
printf "%s\t%dm%d.%03dms\n" $1 $TIME_M $TIME_S $TIME_MS
}
if [ $(getconf CLK_TCK) != 100 ]; then
exit 1;
fi
if [ $# != 1 ]; then
exit 1;
fi
PROC_DIR="/proc/"$1
if [ ! -e $PROC_DIR ]; then
exit 1
fi
USER_TIME=0
SYS_TIME=0
START_TIME=0
while [ -e $PROC_DIR ]; do
read TMP_USER_TIME TMP_SYS_TIME TMP_START_TIME <<< $(cat $PROC_DIR/stat | awk '{print $14, $15, $22;}')
if [ -e $PROC_DIR ]; then
USER_TIME=$TMP_USER_TIME
SYS_TIME=$TMP_SYS_TIME
START_TIME=$TMP_START_TIME
sleep 0.1
else
break
fi
done
read FINISH_TIME <<< $(cat '/proc/self/stat' | awk '{print $22;}')
let "REAL_TIME=($FINISH_TIME - $START_TIME)*10"
print_res_jeffies 'real' $REAL_TIME
print_res_ticks 'user' $USER_TIME
print_res_ticks 'sys' $SYS_TIME
And this is an example that compares my implementation of time and real time:
>time ./sys_intensive > /dev/null
Alarm clock
real 0m10.004s
user 0m9.883s
sys 0m0.034s
In another terminal window I run my_time.sh and give it PID:
>./my_time.sh `pidof sys_intensive`
real 0m10.010ms
user 0m9.780ms
sys 0m0.030ms

find all users who has over N process and echo them in shell

I'm writing script is ksh. Need to find all users who has over N process and echo them in shell.
N reads from ksh.
I know what I should use ps -elf but how parse it, find users with >N process and create array with them. Little troubles with array in ksh. Please help. Maybe simple solutions can help me instead of array creating.
s162103#helios:/home/s162103$ ps -elf
0 S s153308 4804 1 0 40 20 ? 17666 ? 11:03:08 ? 0:00 /usr/lib/gnome-settings-daemon --oa
0 S root 6546 1327 0 40 20 ? 3584 ? 11:14:06 ? 0:00 /usr/dt/bin/dtlogin -daemon -udpPor
0 S webservd 15646 485 0 40 20 ? 2823 ? п╪п╟я─я ? 0:23 /opt/csw/sbin/nginx
0 S s153246 6746 6741 0 40 20 ? 18103 ? 11:14:21 ? 0:00 iiim-panel --disable-crash-dialog
0 S s153246 23512 1 0 40 20 ? 17903 ? 09:34:08 ? 0:00 /usr/bin/metacity --sm-client-id=de
0 S root 933 861 0 40 20 ? 5234 ? 10:26:59 ? 0:00 dtgreet -display :14
...
when i type
ps -elf | awk '{a[$3]++;}END{for(i in a)if (a[i]>N)print i, a[i];}' N=1
s162103#helios:/home/s162103$ ps -elf | awk '{a[$3]++;}END{for(i in a)if (a[i]>N)print i, a[i];}' N=1
root 118
/usr/sadm/lib/smc/bin/smcboot 3
/usr/lib/autofs/automountd 2
/opt/SUNWut/lib/utsessiond 2
nasty 31
dima 22
/opt/oracle/product/Oracle_WT1/ohs/ 7
/usr/lib/ssh/sshd 5
/usr/bin/bash 11
that is not user /usr/sadm/lib/smc/bin/smcboot
there is last field in ps -elf ,not user
Something like this(assuming 3rd field of your ps command gives the user id):
ps -elf |
awk '{a[$3]++;}
END {
for(i in a)
if (a[i]>N)
print i, a[i];
}' N=3
The minimal ps command you want to use here is ps -eo user=. This will just print the username for each process and nothing more. The rest can be done with awk:
ps -eo user= |
awk -v max=3 '{ n[$1]++ }
END {
for (user in n)
if (n[user]>max)
print n[user], user
}'
I recommend to put the count in the first column for readability.
read number
ps -elfo user= | sort | uniq -c | while read count user
do
if (( $count > $number ))
then
echo $user
fi
done
That is best solution and it works!

Resources