#!/usr/bin/env bash
sleep 3 & # Spawn a child
trap '
pgrep -P $$ # Outputs one PID as expected
PIDS=( $( pgrep -P $$ ) ) # Saves an extra nonexistant PID
echo "PIDS: ${PIDS[#]}" # You can see it is the last one
ps -o pid= "${PIDS[#]:(-1)}" ||
echo "Dafuq is ${PIDS[#]:(-1)}?" # Yep, it does not exist!
' 0 1 2 3 15
It outputs
11800
PIDS: 11800 11802
Dafuq is 11802?
It only happens with traps.
Why is a nonexistent PID appended to the array? And how to avoid this odd behaviour?
By using $(...), you've created a subprocess which will execute that code.
Naturally, the parent of that process will be the current shell, so it's going to be listed.
As for the workaround, you could remove that PID from the list. First you have to know how to access the subshell PID: $$ in a script vs $$ in a subshell . Now you can filter it out (nope, it doesn't work):
PIDS=( $( pgrep -P $$ | grep -v ^$BASHPID$ ) )
Related
Using bash version 5, I have a loop that goes through a list, and uses each item in that list in a find command inside the loop, and then append that find result to an empty array.
This code however stops at saving 1st result successfully in pid and script exits. I have tried trap and set -x with no progress.
Anyone understands what's happening here ?
#!/bin/bash
set -e
set -o pipefail
set -vx
## run cleanup on signal 1, 2, 3, 6
trap cleanup 1 2 3 6
cleanup()
{
echo "Caught Signal $? ... cleaning up."
}
r=$(cat /proc/net/tcp |grep " 01 " |awk '{print $10}')
inodes=()
inodes+=($r)
pattern="^.*(proc)\/\K([a-zA-Z0-9_-]*)(?=\/[a-zA-Z0-9]*)?"
con_pids=()
for inode in ${inodes[*]}
do
echo inode number is: $inode
pid=$(find /proc/* -type l -ls 2>/dev/null |grep "socket:\[$inode\]" | grep -m1 -oP $pattern)
echo "code didn't stop"
# echo $?
con_pids+=($pid)
#con_pids=(${con_pids[*]} "$pid")
done
echo ${con_pids[*]}
I have tried multiple different ways to retrieve the exit status of a background process:
Capture pid of each background process, store in an array and then wait for each PID, get return status of each PID and store in a STATUS array.
Con: pid is not a child of this shell
tail --pid= -f /dev/null
Con: exit status is always 0 here
Looked around for various answers on stackoverflow. I am still not able to get it working. Can you please help and let me know where am I going wrong?
PIDS=()
STATUS=()
OVERALL_EXIT=0
# run processes and store pids in array
for target in ${target_list} ; do
./<script_to_execute> ${target} &
PIDS+=$!
done
# wait for all processes to finish and then capture return status of each
for pid in ${PIDS[#]}; do
echo "${pid}"
wait ${pid}
#tail —pid=${pid} -f /dev/null
#ps ax | grep ${pid} | grep -v grep
STATUS+=($?)
done
# looping through the status arr to check exit code for each
i=0
for st in ${STATUS[#]}; do
if [[ ${st} -ne 0 ]]; then
echo "$i failed"
OVERALL_EXIT=1
else
echo "$i finished"
fi
((i+=1))
done
exit ${overall_exit}
PIDS+=$!
...doesn't do what you think it does. Consider:
PIDS=( )
PIDS+=11
PIDS+=22
PIDS+=33
declare -p PIDS
...if what you expect this to output is:
declare -a PIDS='([0]="11" [1]="22" [2]="33")
...you'd be mistaken, because what it actually emits is:
declare -a PIDS='([0]="112233")'
...because += only appends a new array element when the thing on the right-hand side is an array.
Thus, you get a not a child of this shell error because the result of concatenating all your PIDs together into a single string isn't a PID that actually exists.
To fix it, use parens: PIDS+=( "$!" )
To provide an end-to-end example:
#!/usr/bin/env bash
# run four different processes; two exit status 0, one exits status 1, on exits status 2
# ...exits happen at delays ranging between 2-5 seconds.
delays=( 5 3 2 4 )
exits=( 0 0 1 2 )
for idx in "${!delays[#]}"; do
(sleep "${delays[$idx]}"; exit "${exits[$idx]}") &
pids+=( "$!" )
done
exit_status=0
for pid in "${pids[#]}"; do
wait "$pid"; (( exit_status |= $? ))
done
echo "Combined exit status is $exit_status"
exit "$exit_status"
...properly exits after 5 seconds with:
Combined exit status is 3
(This should have been a comment, but code gets formatted wrongly).
Looking at your full code it seems you are trying to implement a basic version of GNU Parallel in bash.
parallel -j0 ./<script_to_execute> ::: ${target_list}
I believe this will do the same as the full code (i.e. return an error if one of the jobs failed).
Command below prints pid of subshell and subshell of subshell:
$ ( ( echo $BASHPID )& echo $BASHPID )& sleep 1
[1] 9885
9885
9887
[1]+ Done ( ( echo $BASHPID ) & echo $BASHPID )
Now command below is more complicated, but it indicates that second subshell is in 'process group' of first subshell:
$ ( ( echo $$ $BASH_SUBSHELL $BASHPID ; export BBB=$BASHPID; ps -e -o pid,pgid,ppid,comm | grep -E "$$|$BBB|PGID" | grep -E "bash|PGID" )& echo $$ $BASH_SUBSHELL $BASHPID; sleep 1 )& sleep 1
[3] 9973
2787 1 9973
2787 2 9975
PID PGID PPID COMMAND
2787 2787 2769 bash
9973 9973 2787 bash
9975 9973 9973 bash
Is there simple way to create similar command which will show unique number for last row in second column?
There's no way to do that since you are detaching the subshells from the current process (PGID) with the & operator therefore will run on a separate process (new PGID).
Check this ones also: How to set process group of a shell script
I would like to have a function in bash, that starts a program in the background, determines the PID of that program and also pipe's its output to sed. I know how to do either one of them separately, but not how to achieve all of them at once.
What I have so far is this:
# Start a program in the background
#
# Arguments:
# 1 - Variable in which to "write" the PID
# 2 - App to execute
# 3 - Arguments to app
#
function start_program_in_background() {
RC=$1; shift
# Start program in background and determine PID
BIN=$1; shift
( $BIN $# & echo $! >&3 ) 3>PID | stdbuf -o0 sed -e 's/a/b/' &
# ALTERNATIVE $BIN $# > >( sed .. ) &
# Write PID to variable given as argument 1
PID=$(<PID)
# when using ALTERNATIVEPID=$!
eval "$RC=$PID"
echo "$BIN ---PID---> $PID"
}
The way I extract the PID is inspired by [1]. There is a second variant in the comments. Both of them show the output of the background processes when executing a script that starts programs using above function, but there is no output when I pipe
[1] How to get the PID of a process that is piped to another process in Bash?
Any ideas?
Solution:
I came up with this myself thanks to some of the helpful comments. In order to being able to mark as solved, I'm posting the working solution here.
# Start a program in the background
#
# Arguments:
# 1 - Variable in which to "write" the PID
# 2 - App to execute
# 3 - Arguments to app
#
function start_program_in_background() {
RC=$1; shift
# Create a temporary file to store the PID
FPID=$(mktemp)
# Start program in background and determine PID
BIN=$1; shift
APP=$(basename $BIN)
( stdbuf -o0 $BIN $# 2>&1 & echo $! >&3 ) 3>$FPID | \
stdbuf -i0 -o0 sed -e "s/^/$APP: /" |\
stdbuf -i0 -o0 tee /tmp/log_${APP} &
# Need to sleep a bit to make sure PID is available in file
sleep 1
# Write PID to variable given as argument 1
PID=$(<$FPID)
eval "$RC=$PID"
rm $FPID # Remove temporary file holding PID
}
I want to check how long time my program takes. Then I using "/usr/bin/time my_program". When it takes more than 5 seconds, I want to kill it. I tried "kill -9 TIME_S_PID", time is killed, but my_program is still running. So how to kill my_program?
Thanks.
I resolved this using 2 scripts below:
the first I name it mytime
#!/bin/bash
##############################################################################################################
# This script is used to perform a /usr/bin/time over a command and deliver the kill signal to the good exe
# Usage : mytime command args
# The result will be in a file name /tmp/command.PID.txt where pid is the current pid
# example:
# ./mytime sleep 10000 &
# kill pid of mytime
# cat /tmp/sleep.*.txt
##############################################################################################################
# store the current pid
P=$$
CMD=$1
signalisation ()
{
# deliver the signal to good pid ie the son of /usr/bin/time
S=$1
# echo Signal $S >>/tmp/trace_$P.txt
PF=$(cat /tmp/pid$P.txt)
rm /tmp/pid$P.txt
# echo pid du fils de time : $PF >>/tmp/trace_$P.txt
kill $S $PF
}
trap "signalisation 1" 1
trap "signalisation 2" 2
trap "signalisation 3" 3
trap "signalisation 15" 15
/usr/bin/time -f '%e %M # Elapsed real time (in seconds) and Memory Max' -o /tmp/$CMD.$P.txt storepid /tmp/pid$P.txt $* &
wait %1
exit $?
and this one used by the previous I named it storepid
#!/bin/bash
###########################################################################################
# this script store the pid in the file specified by the first argument
# the it executes the command defined by the others arguments
# example:
# storepid /tmp/mypid.txt sleep 100
# result sleep 100 secondes , but you can kill it before using kill $(cat /tmp/mypid.txt)
###########################################################################################
WHERE=$1
shift
echo $$ >$WHERE
exec $*
Note that killall, as its name suggests, will kill all instances of your program. A better way would be to make a wrapper script:
#!/bin/sh
echo "$$" # print the PID of the current process
exec my_program
Then you execute /usr/bin/time my_wrapper_script, which prints PID of this instance of your program, and kill it when you want with kill -9 "$my_prog_pid".
As #Soulseekah said, killall my_program works.