我必须运行 Bash 命令。但是这个命令需要几分钟才能运行。
如果我正常(同步)执行此命令,我的应用程序将挂起,直到命令完成运行。
如何从 Perl 脚本异步运行 Bash 命令?
我必须运行 Bash 命令。但是这个命令需要几分钟才能运行。
如果我正常(同步)执行此命令,我的应用程序将挂起,直到命令完成运行。
如何从 Perl 脚本异步运行 Bash 命令?
执行此操作的正常方法是使用fork
. 您将拥有您的脚本分支,然后孩子将调用Bash 脚本exec
或system
在 Bash 脚本上调用(取决于孩子是否需要处理 Bash 脚本的返回码,或以其他方式与之交互)。
那么你的父母可能想要一个wait
和/或一个SIGCHILD
处理程序的组合。
如何处理它的确切细节在很大程度上取决于您的情况和确切需求。
如果您不关心结果,则可以使用system("my_bash_script &");
. 它会立即返回,并且脚本会执行需要完成的操作。
我有两个文件:
$ cat wait.sh
#!/usr/bin/bash
for i in {1..5}; { echo "wait#$i"; sleep 1;}
$cat wait.pl
#!/usr/bin/perl
use strict; use warnings;
my $t = time;
system("./wait.sh");
my $t1 = time;
print $t1 - $t, "\n";
system("./wait.sh &");
print time - $t1, "\n";
输出:
wait#1
wait#2
wait#3
wait#4
wait#5
5
0
wait#1
wait#2
wait#3
wait#4
wait#5
可以看出,第二次调用立即返回,但它一直在写入标准输出。
如果您需要与孩子交流,那么您需要使用fork
和重定向STDIN
和STDOUT
(和STDERR
)。或者您可以使用IPC::Open2或IPC::Open3包。无论如何,在调用者退出之前等待孩子退出总是一个好习惯。
如果你想等待执行的进程,你可以在 Bash 中尝试这样的事情:
#!/usr/bin/bash
cpid=()
for exe in script1 script2 script3; do
$exe&
cpid[$!]="$exe";
done
while [ ${#cpid[*]} -gt 0 ]; do
for i in ${!cpid[*]}; do
[ ! -d /proc/$i ] && echo UNSET $i && unset cpid[$i]
done
echo DO SOMETHING HERE; sleep 2
done
该脚本首先异步启动 script# 并将 pid 存储在一个名为cpid
. 然后有一个循环;它浏览它们是否仍在运行(/proc/ 存在)。如果不存在,UNSET <PID>
则显示文本并从数组中删除 PID。
它不是万无一失的,好像DO SOMETHING HERE
部分运行时间很长,然后可以将相同的 PID 添加到另一个进程。但它在一般环境中运行良好。
但这种风险也可以降低:
#!/usr/bin/bash
# Enable job control and handle SIGCHLD
set -m
remove() {
for i in ${!cpid[*]}; do
[ ! -d /proc/$i ] && echo UNSET $i && unset cpid[$i] && break
done
}
trap "remove" SIGCHLD
#Start background processes
cpid=()
for exe in "script1 arg1" "script2 arg2" "script3 arg3" ; do
$exe&
cpid[$!]=$exe;
done
#Non-blocking wait for background processes to stop
while [ ${#cpid[*]} -gt 0 ]; do
echo DO SOMETHING; sleep 2
done
此版本使脚本能够在异步子进程退出时接收 SIGCHLD 信号。如果收到 SIGCHLD,它会异步查找第一个不存在的进程。等待的while循环减少了很多。
您可以使用threads
异步启动 Bash,
use threads;
my $t = async {
return scalar `.. long running command ..`;
};
然后手动测试线程是否准备好加入,并以非阻塞方式获取输出,
my $output = $t->is_joinable() && $t->join();