Unix Bash 脚本即使使用 PID 文件也会启动多次

问题描述 投票:0回答:1

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

该文件夹是多个数据记录器的目的地,它们通过 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
php bash shell pid inotify
1个回答
0
投票

您在

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)

第一个

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

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

echo <(cat </dev/null)

您会看到使用该语法时

echo
看到的参数是文件名,可能类似于
/dev/fd/XX

摆脱子 shell 的一个主要优点是:循环在主 shell 作用域中执行,因此一旦循环终止,您在循环中执行的变量的任何更改都可以在循环外部看到。 在这里这可能并不重要,但是,记住我的话,你会意识到它在很多很多情况下所带来的巨大差异。

为了说明子 shell 发生的情况,这里有一个小代码片段:

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。 我认为没有办法摆脱这个子外壳。

© www.soinside.com 2019 - 2024. All rights reserved.