0

I want to restart celery from server, because the celery has many process, to I write this script to query all process id and kill it:

#
# stop celery process
#
PID=`ps -ef|grep -w ${CELERY_PROGRAM_NAME}|grep -v grep|cut -c 9-15`
if [ -z "${PID}" ]; then
  echo "Process aready down..."
else
    array=(${PID//\n/})
    for var in "${array[@]}"
    do
      single_pid=`echo ${var} | awk 'gsub(/^ *| *$/,"")' `
      if [[ ${single_pid} -gt 1 ]]; then
          kill -15 "${single_pid}"
      else
          echo "Process ${PROGRAM_NAME} not found"
      fi
    done
fi

from the log, I found the pid did not convert to array, the next step did not split correctly. I run this script remotely from GitHub Actions. This is the log output from GitHub Actions:

======CMD======
cd /opt/apps/pydolphin
. /opt/apps/pydolphin/restart.sh

======END======
err: +/opt/apps/pydolphin/restart.sh:16> PROGRAM_NAME=schedulespider.py 
err: +/opt/apps/pydolphin/restart.sh:17> CELERY_PROGRAM_NAME=celery 
err: +/opt/apps/pydolphin/restart.sh:18> PYTHON_BIN_PATH=/usr/bin/python3 
err: +/opt/apps/pydolphin/restart.sh:23> PID=+/opt/apps/pydolphin/restart.sh:23> ps -ef
err: +/opt/apps/pydolphin/restart.sh:23> PID=+/opt/apps/pydolphin/restart.sh:23> grep -w celery
err: +/opt/apps/pydolphin/restart.sh:23> PID=+/opt/apps/pydolphin/restart.sh:23> grep -v grep
err: +/opt/apps/pydolphin/restart.sh:23> PID=+/opt/apps/pydolphin/restart.sh:23> cut -c 9-15
err: +/opt/apps/pydolphin/restart.sh:23> PID='  9777 
err:   9778 
err:   9779 
err:   9865 
err:   9867 
err:   9868 ' 
err: +/opt/apps/pydolphin/restart.sh:24> [ -z '  9777 
err:   9778 
err:   9779 
err:   9865 
err:   9867 
err:   9868 ' ']'
err: +/opt/apps/pydolphin/restart.sh:27> array=( '  9777 
err:   9778 
err:   9779 
err:   9865 
err:   9867 
err:   9868 ' ) 
err: +/opt/apps/pydolphin/restart.sh:28> var=  9777 
err:   9778 
err:   9779 
err:   9865 
err:   9867 
err:   9868 
err: +/opt/apps/pydolphin/restart.sh:30> single_pid=+/opt/apps/pydolphin/restart.sh:30> echo '  9777 
err:   9778 
err:   9779 
err:   9865 
err:   9867 
2021/07/19 06:00:52 Process exited with status 1
err:   9868 '
err: +/opt/apps/pydolphin/restart.sh:30> single_pid=+/opt/apps/pydolphin/restart.sh:30> awk 'gsub(/^ *| *$/,"")'
err: +/opt/apps/pydolphin/restart.sh:30> single_pid='9777
err: 9778
err: 9779
err: 9865
err: 9867
err: 9868' 
err: +/opt/apps/pydolphin/restart.sh:31> [[ '9777
err: 9778
err: 9779
err: 9865
err: 9867
err: 9868' -gt 1/opt/apps/pydolphin/restart.sh:31: bad math expression: operator expected at `9778\n9779\n...'
err:  ]]

I read my script and did not found where is going wrong, what should I do to make it work?

4
  • 4
    Do you have the pkill program available? – Jeff Schaller Jul 19 at 12:48
  • Yes, I could using pkill, but I still want to find where my code going wrong, I want to learn the bash.@JeffSchaller – Dolphin Jul 19 at 12:50
  • Is your interactive shell zsh? I noticed a ". /opt/apps/pydolphin/restart.sh" in your question and steeldriver's answer indicates that zsh could be playing a role. For future questions, be careful you know which shell is executing your code! (and word/tag questions appropriately). Thanks! – Jeff Schaller Jul 19 at 13:57
  • 1
    That xtrace output format suggests the script is interpreted by zsh, not bash. – Stéphane Chazelas Jul 19 at 14:28
2

If you are determined to write your own "kill loop" instead of simply using pkill, then at least use pgrep to get a list of PIDs, unencumbered by possible leading / trailing whitespace - instead of trying to slice and dice the output of ps:

array=($(pgrep -- "${CELERY_PROGRAM_NAME}"))

or simply loop over the pgrep output directly

for single_pid in $(pgrep -- "${CELERY_PROGRAM_NAME}"); do ...

pgrep produces a list of PIDs, one per line, and the default bash IFS will split on contiguous whitespace including newlines to map them to an array ex.

$ pgrep ssh
1194
3688
22642
22754

$ array=($(pgrep ssh))

$ declare -p array
declare -a array=([0]="1194" [1]="3688" [2]="22642" [3]="22754")

Alternatively could use readarray -t array < <(pgrep ssh).

There's no need to test for string emptiness since the loop will simply not run if there are no elements.

As to why your implementation is not working, ${PID//\n/} would remove literal n characters from PID1. Assuming PID only contains numeric values, that should do nothing, and array=(${PID//\n/}) should have created an array of individual PIDs, also removing leading and trailing whitespace so that further processing to get a single_pid should be unnecessary.

The fact that it apparently doesn't (based on your error output) suggests one of two causes:

  1. you have modified the shell's IFS value

  2. the shell you are using doesn't word-split unquoted variable expansions. zsh, which the format of your xtrace output suggests you're using has that default behavior for example, though does IFS-splitting upon command substitution, so the code above would still work in that shell. To split on newline in zsh, you'd rather use:

    array=(${(f)"$(pgrep -- $CELERY_PROGRAM_NAME)"})
    

    though, which like bash's readarray would remove the dependency on the current value of $IFS.


1 to actually remove newlines, you could use ${PID//$'\n'/}

2

another alternative to avoid pgrep/pkill is to customize ps's output.

ps can be customize in both process to list and format/fields to print.

note that ps -ef will list all process with many fields, and could be cumbersome to fetch pid.

while ps -e -o pid,comm will give a two columns output

 PID COMMAND
   1 systemd
   2 kthreadd
   3 rcu_gp
   4 rcu_par_gp
   9 mm_percpu_wq
  10 ksoftirqd/0
    (...)
1002 gdm3
1013 sshd
1031 php-fpm7.4
1032 php-fpm7.4
1044 nginx
1065 nmbd
     (many more lines)

(or -o pid= -o comm= to remove the headers).

If the process I am looking for is in ${CELERY_PROGRAM_NAME} (and doesn't contain backslash or blank characters), I can use

 ps -e -o pid= -o comm= | awk -v proc="${CELERY_PROGRAM_NAME}" '$2==proc { print $1}'

to fetch pids.

using

PID=$(ps -e -o pid= -o comm= | awk -v proc="${CELERY_PROGRAM_NAME}" '$2==proc { print $1})

after checking non emptiness, and given that ${PID} is a newline-delimited list of pids, I can use (assuming $IFS contains newline as it does by default):

for pid2 in ${PID}
do 
   ...
done

(if using zsh as the format of your xtrace output suggests you do, replace ${PID} with ${=PID} to do $IFS-splitting like bash does by default, or with ${(f)PID} to split on newline regardless of the value of $IFS).

select

some select of ps (see man ps for all )

  • ps -p 1234 -o tty,args (list tty and args of pid 1234 )
  • ps -t pts/1,pts/3 -f (list all from tty pts/1 and pts/3 )
  • ps -u archemar (list all process belonging to archemar)

The HP/UX and procps implementations of ps also support a -C option to query processes based on their name.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.