为什么 bash 中的 pid 不拆分为数组

为什么 bash 中的 pid 不拆分为数组

我想从服务器重新启动 celery,因为 celery 有很多进程,我编写这个脚本来查询所有进程 id 并杀死它:

#
# 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

从日志中,我发现pid没有转换为数组,下一步没有正确分割。我从 GitHub Actions 远程运行此脚本。这是 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:  ]]

我读了我的脚本,但没有发现哪里出了问题,我该怎么做才能使它工作?

答案1

如果您决定编写自己的“终止循环”而不是简单地使用pkill,那么至少使用pgrep来获取 PID 列表,不受可能的前导/尾随空格的阻碍 - 而不是尝试对 的输出进行切片和切块ps

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

pgrep或者直接循环输出

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

pgrep生成一个 PID 列表,每行一个,默认的 bash IFS 将在连续的空格上分割包括换行符将它们映射到数组 ex。

$ pgrep ssh
1194
3688
22642
22754

$ array=($(pgrep ssh))

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

或者可以使用readarray -t array < <(pgrep ssh).

无需测试字符串是否为空,因为如果没有元素,循环将不会运行。

至于为什么您的实现不起作用,将从1${PID//\n/}中删除文字n字符。假设只包含数值,那应该什么也不做,并且PIDPIDarray=(${PID//\n/}) 应该创建了一个单独的 PID 数组,还删除了前导和尾随空格,这样就single_pid不需要进一步处理来获取 PID。

事实上,它显然没有(基于您的错误输出)表明以下两个原因之一:

  1. 您已修改 shell 的 IFS 值

  2. 您使用的 shell 不会对不带引号的变量扩展进行分词。zsh,您的输出格式xtrace表明您正在使用的文件具有默认行为,例如,尽管在命令替换时进行 IFS 分割,所以上面的代码仍然可以在该 shell 中工作。要在 中按换行符分割zsh,您宁愿使用:

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

    不过,这bashreadarray消除对当前值的依赖$IFS


1要实际删除换行符,您可以使用${PID//$'\n'/}

答案2

另一种要避免的选择pgrep/pkill是自定义ps的输出。

ps可以在列表过程和打印格式/字段中进行自定义。

请注意,它将ps -ef列出具有许多字段的所有进程,并且获取 pid 可能会很麻烦。

whileps -e -o pid,comm将给出两列输出

 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)

(或-o pid= -o comm=删除标题)。

如果我正在寻找的进程位于${CELERY_PROGRAM_NAME}(并且不包含反斜杠或空白字符),我可以使用

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

获取 pid。

使用

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

检查非空后,并考虑到这${PID}是一个以换行符分隔的 pid 列表,我可以使用(假设$IFS包含换行符,默认情况下):

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

(如果使用zsh作为输出的格式xtrace建议您这样做,请替换${PID}${=PID}to do $IFS-splitting 就像bash默认情况下那样,或者替换为${(f)PID}在换行符上拆分,无论 的值如何$IFS)。

选择

一些选择ps(查看man ps全部)

  • ps -p 1234 -o tty,args(列出 pid 1234 的 tty 和 args )
  • ps -t pts/1,pts/3 -f(列出 tty pts/1 和 pts/3 中的所有内容)
  • ps -u archemar(列出属于 archemar 的所有进程)

HP/UX 和 procps 实现ps还支持-C根据进程名称查询进程的选项。

相关内容