我编写了第一个 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
您在
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。 我认为没有办法摆脱这个子外壳。