我运行了一些bash脚本,但它们可能需要几个小时才能完成,在此期间它们会发出下载速度,ETA和类似信息。我需要在perl中捕获这些信息,但是我遇到了一个问题,我无法逐行读取输出(除非我遗漏了一些东西)。
有任何帮助解决这个问题吗?
编辑:为了更好地解释这个,我正在运行几个bash脚本,我希望使用带有perl的gtk来生成方便的进度条。 目前我为每个我希望运行的bash脚本运行2个线程,一个用于更新图形信息的主线程。它看起来像这样(尽可能减少): my $command1 = threads->create(\&runCmd, './bash1', \@out1);
my $controll1 = threads->create(\&monitor, $command1, \@out1);
my $command1 = threads->create(\&runCmd, 'bash2', \@out2);
my $controll2 = threads->create(\&monitor, $command2, \@out2);
sub runCmd{
my $cmd = shift;
my @bso = shift;
@bso = `$cmd`
}
sub monitor{
my $thrd = shift;
my @bso = shift;
my $line;
while($thrd->is_running()){
while($line = shift(@bso)){
## I check the line and do things with it here
}
## update anything the script doesn't tell me here.
sleep 1;# don't cripple the system polling data.
}
## thread quit, so we remove the status bar and check if another script is in the queue, I'm omitting this here.
}
答案 0 :(得分:8)
而不是线程和``,使用:
open my $fh, '-|', 'some_program --with-options';
以这种方式打开几个文件句柄(需要运行多个程序),然后使用IO::Select
从它们轮询数据。
简单的例子。
我们假设我的shell脚本如下所示:
=> cat test.sh
#!/bin/bash
for i in $( seq 1 5 )
do
sleep 1
echo "from $$ : $( date )"
done
它的输出可能如下所示:
=> ./test.sh from 26513 : Fri Aug 7 08:48:06 CEST 2009 from 26513 : Fri Aug 7 08:48:07 CEST 2009 from 26513 : Fri Aug 7 08:48:08 CEST 2009 from 26513 : Fri Aug 7 08:48:09 CEST 2009 from 26513 : Fri Aug 7 08:48:10 CEST 2009
现在,让我们写一个multi-test.pl
:
#!/usr/bin/perl -w
use strict;
use IO::Select;
my $s = IO::Select->new();
for (1..2) {
open my $fh, '-|', './test.sh';
$s->add($fh);
}
while (my @readers = $s->can_read()) {
for my $fh (@readers) {
if (eof $fh) {
$s->remove($fh);
next;
}
my $l = <$fh>;
print $l;
}
}
正如你所看到的,没有叉子,没有线程。这就是它的工作原理:
=> time ./multi-test.pl from 28596 : Fri Aug 7 09:05:54 CEST 2009 from 28599 : Fri Aug 7 09:05:54 CEST 2009 from 28596 : Fri Aug 7 09:05:55 CEST 2009 from 28599 : Fri Aug 7 09:05:55 CEST 2009 from 28596 : Fri Aug 7 09:05:56 CEST 2009 from 28599 : Fri Aug 7 09:05:56 CEST 2009 from 28596 : Fri Aug 7 09:05:57 CEST 2009 from 28599 : Fri Aug 7 09:05:57 CEST 2009 from 28596 : Fri Aug 7 09:05:58 CEST 2009 from 28599 : Fri Aug 7 09:05:58 CEST 2009 real 0m5.128s user 0m0.060s sys 0m0.076s
答案 1 :(得分:3)
反引号和qx //运算符都会阻塞,直到子流程完成。您需要在管道上打开bash脚本。如果您需要它们是非阻塞的,请将它们作为文件句柄打开,必要时使用open2或open3,然后将句柄放入select()并等待它们变得可读。
我遇到了类似的问题 - 我有一个非常长时间运行的过程(一个可以运行数周的服务),我用qx打开了//。问题是该程序的输出最终超出了内存限制(我的架构大约2.5G)。我通过打开管道上的子命令解决了它,然后只保存了最后1000行输出。在这样做时,我注意到qx //表单只在命令完成后打印输出,但管道表单能够在发生时打印输出。
我没有方便的代码,但如果你可以等到明天,我会发布我所做的。
答案 2 :(得分:2)
有关您可以执行的一些操作,请参阅perlipc(进程间通信)。 Piped打开,IPC :: Open3很方便。
答案 3 :(得分:1)
while (<STDIN>) { print "Line: $_"; }
问题是某些应用程序不会逐行显示信息,而是更新一行直到它们完成。是你的情况吗?
答案 4 :(得分:1)
这是用于显示进度条的GTK2代码。
#!/usr/bin/perl
use strict;
use warnings;
use Glib qw/TRUE FALSE/;
use Gtk2 '-init';
my $window = Gtk2::Window->new('toplevel');
$window->set_resizable(TRUE);
$window->set_title("command runner");
my $vbox = Gtk2::VBox->new(FALSE, 5);
$vbox->set_border_width(10);
$window->add($vbox);
$vbox->show;
# Create a centering alignment object;
my $align = Gtk2::Alignment->new(0.5, 0.5, 0, 0);
$vbox->pack_start($align, FALSE, FALSE, 5);
$align->show;
# Create the Gtk2::ProgressBar and attach it to the window reference.
my $pbar = Gtk2::ProgressBar->new;
$window->{pbar} = $pbar;
$align->add($pbar);
$pbar->show;
# Add a button to exit the program.
my $runbutton = Gtk2::Button->new("Run");
$runbutton->signal_connect_swapped(clicked => \&runCommands, $window);
$vbox->pack_start($runbutton, FALSE, FALSE, 0);
# This makes it so the button is the default.
$runbutton->can_default(TRUE);
# This grabs this button to be the default button. Simply hitting the "Enter"
# key will cause this button to activate.
$runbutton->grab_default;
$runbutton->show;
# Add a button to exit the program.
my $closebutton = Gtk2::Button->new("Close");
$closebutton->signal_connect_swapped(clicked => sub { $_[0]->destroy;Gtk2->main_quit; }, $window);
$vbox->pack_start($closebutton, FALSE, FALSE, 0);
$closebutton->show;
$window->show;
Gtk2->main;
sub pbar_increment {
my ($pbar, $amount) = @_;
# Calculate the value of the progress bar using the
# value range set in the adjustment object
my $new_val = $pbar->get_fraction() + $amount;
$new_val = 0.0 if $new_val > 1.0;
# Set the new value
$pbar->set_fraction($new_val);
}
sub runCommands {
use IO::Select;
my $s = IO::Select->new();
for (1..2) {
open my $fh, '-|', './test.sh';
$s->add($fh);
}
while (my @readers = $s->can_read()) {
for my $fh (@readers) {
if (eof $fh) {
$s->remove($fh);
next;
}
my $l = <$fh>;
print $l;
pbar_increment($pbar, .25) if $l =~ /output/;
}
}
}
请参阅the perl GTK2 docs了解详情
答案 5 :(得分:1)
我使用这个子例程和方法来记录我的外部命令。它被称为:
open($logFileHandle, "mylogfile.log");
logProcess($logFileHandle, "ls -lsaF", 1, 0); #any system command works
close($logFileHandle);
以下是子程序:
#******************************************************************************
# Sub-routine: logProcess()
# Author: Ron Savage
# Date: 10/31/2006
#
# Description:
# This sub-routine runs the command sent to it and writes all the output from
# the process to the log.
#******************************************************************************
sub logProcess
{
my $results;
my ( $logFileHandle, $cmd, $print_flag, $no_time_flag ) = @_;
my $logMsg;
my $debug = 0;
if ( $debug ) { logMsg($logFileHandle,"Opening command: [$cmd]", $print_flag, $no_time_flag); }
if ( open( $results, "$cmd |") )
{
while (<$results>)
{
chomp;
if ( $debug ) { logMsg($logFileHandle,"Reading from command: [$_]", $print_flag, $no_time_flag); }
logMsg($logFileHandle, $_, $print_flag, $no_time_flag);
}
if ( $debug ) { logMsg($logFileHandle,"closing command.", $print_flag, $no_time_flag); }
close($results);
}
else
{
logMsg($logFileHandle, "Couldn't open command: [$cmd].")
}
}
#******************************************************************************
# Sub-routine: logMsg()
# Author: Ron Savage
# Date: 10/31/2006
#
# Description:
# This sub-routine prints the msg and logs it to the log file during the
# install process.
#******************************************************************************
sub logMsg
{
my ( $logFileHandle, $msg, $print_flag, $time_flag ) = @_;
if ( !defined($print_flag) ) { $print_flag = 1; }
if ( !defined($time_flag) ) { $time_flag = 1; }
my $logMsg;
if ( $time_flag )
{ $logMsg = "[" . timeStamp() . "] $msg\n"; }
else
{ $logMsg = "$msg\n"; }
if ( defined($logFileHandle)) { print $logFileHandle $logMsg; }
if ( $print_flag ) { print $logMsg; }
}
答案 6 :(得分:0)
运行完全控制其输入和输出的子进程的最简单方法是IPC::Open2
模块(如果你想捕获STDERR,也可以IPC::Open3
),但是如果你想要的话就是问题一次处理多个,或者特别是如果你想在GUI中处理它,就是阻塞。如果您只是执行<$fh>
类型的读取,它将会阻止,直到您输入为止,可能会占用整个UI。如果子进程是交互式的,那就更糟了,因为你很容易死锁,孩子和父母都在等待另一个人的输入。您可以编写自己的select
循环并执行非阻塞I / O,但这不值得。我的建议是使用POE
,POE::Wheel::Run
与子进程交互,POE::Loop::Gtk
将POE包含在GTK runloop中。