【问题标题】:Unix Bash Script starts multiple times even with PID File即使使用 PID 文件,Unix Bash 脚本也会多次启动
【发布时间】:2017-06-02 18:52:03
【问题描述】:

我编写了我的第一个 bash 脚本,它使用“inotify”函数检查文件夹的更改并启动一些操作。整个进程由nohup作为后台进程运行。

该文件夹是多个 Datalogger 的目的地,它们通过 ftp 将 zip 格式的文件推送到不同的子文件夹中。 bash 脚本解压缩文件并随后启动一个 php 脚本,该脚本正在处理 zip 文件的内容。

我的问题:有时 bash 脚本会给我如下错误:

- No zipfiles found.
- unzip:  cannot find zipfile...

这不应该发生,因为文件存在并且我可以在终端中运行相同的命令而不会出错。我之前遇到过同样的问题,当时我多次运行脚本时,所以我想这是导致问题的原因。

我尝试使用位于我的主目录中的 PID 文件来解决问题。出于某种原因,它仍然运行 bash 脚本的两个实例。如果我尝试运行另一个实例,它会按预期显示警告“进程已在运行”(参见程序代码)。当我手动终止第二个实例的进程(kill $$)时,它会在一段时间后重新启动,并且再次运行该进程的两个实例。

#!/bin/bash

PIDFILE=/home/PIDs/myscript.pid

if [ -f $PIDFILE ]
then
  PID=$(cat $PIDFILE)
  ps -p $PID > /dev/null 2>&1
  if [ $? -eq 0 ]
  then
    echo "Process already running"
    exit 1
  else
    ## Process not found assume not running
    echo $$ > $PIDFILE
    if [ $? -ne 0 ]
    then
      echo "Could not create PID file"
      exit 1
    fi
  fi
else
  echo $$ > $PIDFILE
  if [ $? -ne 0 ]
  then
    echo "Could not create PID file"
    exit 1
  fi
fi

while true;
do inotifywait -q -r -e move -e create --format %w%f /var/somefolder | while read FILE

do
    dir=$(dirname $FILE)
    filename=${FILE:$((${#dir}+1))}

    if [[ "$filename" == *.zip ]];
           then
                unzip $FILE
                php somephpscript $dir 
    fi
done
done

ps -ef 的输出如下所示:

UID  PID  PPID C STIME TTY   TIME     CMD

root 1439 1433 0 11:19 pts/0 00:00:00 /bin/bash /.../my_script 
root 3488 1439 0 15:10 pts/0 00:00:00 /bin/bash /.../my_script 

如您所见,第二个实例 Parent-PID 是脚本本身

编辑:我按照 Fred 的建议更改了 bash 脚本。源代码现在看起来像这样:

#!/bin/bash

PIDFILE=/home/PIDs/myscript.pid

if [ -f $PIDFILE ]
then
  PID=$(cat $PIDFILE)
  ps -p $PID > /dev/null 2>&1
  if [ $? -eq 0 ]
  then
    echo "Process already running"
    exit 1
  else
    ## Process not found assume not running
    echo $$ > $PIDFILE
    if [ $? -ne 0 ]
    then
      echo "Could not create PID file"
      exit 1
    fi
  fi
else
  echo $$ > $PIDFILE
  if [ $? -ne 0 ]
  then
    echo "Could not create PID file"
    exit 1
  fi
fi

while read -r FILE
do
    dir=$(dirname $FILE)
    filename=${FILE:$((${#dir}+1))}

    if [[ "$filename" == *.zip ]];
           then
                unzip $FILE
                php somephpscript $dir 
    fi


done < <(inotifywait -q -m -r -e move -e create --format %w%f /var/somefolder)

ps -ef 的输出仍然显示两个实例:

UID       PID   PPID  C STIME TTY      TIME     CMD
root      7550  7416  0 15:59 pts/0    00:00:00 /bin/bash /.../my_script
root      7553  7550  0 15:59 pts/0    00:00:00 /bin/bash /.../my_script
root      7554  7553  0 15:59 pts/0    00:00:00 inotifywait -q -m -r -e move -e create --format %w%f /var/somefolder

【问题讨论】:

  • 您是说脚本在第二次启动时会显示警告消息,然后在“exit 1”语句之后不会停止?如果你真的在交互式 shell 中尝试“杀死 $$”,那什么也做不了,因为 $$ 是 shell 的 PID,而 shell 发送自己的信号什么都不做。
  • 我必须说第一个剧本相当有野心!在单括号条件 [ ] 中,您应该始终引用变量,例如 [ -f "$PIDFILE" ]。好吧,你应该到处引用它们。通常建议您使用小写的变量名以避免与环境变量冲突(尽管我已经知道自己忽略了这一点......)。
  • @Fred 当我第二次启动脚本时,它会按照预期的方式工作。它给出错误消息并且不启动该过程。我不明白您的第二部分,但我所说的 kill $$ 的意思是,我使用命令“ps -ef”手动搜索 PID,然后将其杀死。感谢您的建议,我将在我的源代码中进行更改。我必须说我从另一个论坛帖子中复制了带有 PID 文件的第一部分,但其余部分仍然相当困难,因为我没有在这个示例中添加更多操作!
  • 当您写“bash 脚本正在运行 bash 脚本的第二个实例”时,您的意思是您提供代码的 bash 脚本正在调用自身吗?还是您的意思是第一个脚本调用了第二个脚本(可能是 PHP 脚本)两次?
  • 我的意思是它以某种方式自称。这里 ps -ef 的两个条目: 1. root 1439 1433 0 11:19 pts/0 00:00:00 /bin/bash /.../my_script 2. root 3488 1439 0 15:10 pts/0 00:00 :00 /bin/bash /.../my_script

标签: php bash shell pid inotify


【解决方案1】:

您在ps 输出中看到两行,并假设这意味着您的脚本已启动两次,但事实并非如此。

您将inotifywait 通过管道传递到while 循环中(没关系)。您可能没有意识到,这样做会导致 Bash 创建一个子 shell 来执行 while 循环。该子shell 不是整个脚本的完整副本。

如果你杀死那个子shell,因为while true 循环,它会立即重新创建。请注意inotifywait 有一个--monitor 选项;我没有对你的脚本进行足够详细的研究,但也许你可以通过使用它来取消外部 while 循环。

还有另一种编写循环的方法,它不会消除子shell,但具有其他优点。尝试类似:

while IFS= read -r FILE
do
  BODY OF THE LOOP
done < <(inotifywait --monitor OTHER ARGUMENTS)

第一个&lt; 表示输入重定向,&lt;( ) 语法表示“执行此命令,将其输出传送到 FIFO,并给我 FIFO 路径,以便我可以从这个特殊文件重定向以提供其输出到循环”。

你可以通过这样做来感受我的意思:

echo <(cat </dev/null)

您会看到echo 在使用该语法时看到的参数是一个文件名,可能类似于/dev/fd/XX

摆脱 subshel​​l 有一个主要优势:循环在主 shell 范围内执行,因此一旦循环终止,您在循环中执行的变量的任何更改都可以在循环外看到。在这里可能无关紧要,但请记住我的话,您会体会到它在很多很多情况下所产生的巨大差异。

为了说明子shell会发生什么,这里有一个小代码sn-p:

while IFS= read -r line
do
  echo Main $$ $BASHPID
  echo $line
done < <(echo Subshell $$ $BASHPID)

特殊变量$$ 包含主shell PID,特殊变量BASHPID 包含当前子shell(如果没有启动子shell,则为主shell PID)。您将看到主 shell PID 在循环和进程替换中是相同的,但 BASHPID 发生了变化,说明启动了一个子 shell。我认为没有办法摆脱那个 subshel​​l。

【讨论】:

  • 感谢您的回复,虽然我不完全理解。
  • 使用
  • 能否请您发布您的非工作代码,以便我可以检查语法错误在哪里?我没有看到上面的代码有问题,而且我每天都使用这种语法,所以可能还有其他问题导致了问题。请注意 之前有空格
  • 也许是您的 inotifywait 命令因某种原因而失败?我发现在网上搜索此错误的第一个问题与安装 CIFS 共享的问题有关。
  • 我再次更改了源代码,现在它可以工作了。可能有些复制失败。仍然有两个实例,如上所示...刚刚看到第二个实例是 inotify 进程的父级。你知道为什么吗?
猜你喜欢
  • 1970-01-01
  • 2018-01-29
  • 1970-01-01
  • 2012-09-17
  • 1970-01-01
  • 1970-01-01
  • 2016-08-11
  • 2018-08-30
  • 1970-01-01
相关资源
最近更新 更多