1

我正在维护一个糟糕的嵌套脚本结构,它定期从键盘获取用户输入,并且我正在尝试编写一个脚本来自动执行此输入。本质上,脚本结构如下所示:

  • cmd1.csh 取3行输入然后调用cmd2.csh,然后正常退出
  • cmd2.csh 调用 cmd3.pl 两次,然后取 1 行输入,然后正常退出
  • cmd3.pl 接受 1 行输入并正常退出。

不幸的是,我无法永久更改这些脚本中的任何一个,所以我能做的任何事情都必须作为整个脚本链的包装器来完成。

我已经尝试了以下 perl 脚本的几种变体,但也许我只是没有达到引号和转义的神奇组合:

$cmd = "echo 'a\\
b\\
c\\
d\\
e\\
f' | ~/pltest/cmd1.csh";

system_tcsh

sub system_tcsh
{
   @args = ("tcsh", "-c", shift);
   return system(@args);
}

问题似乎是,一旦调用了一次 cmd3.pl 并读取了 d,则第二次调用 cmd3.pl 或 cmd2.csh 都不会读取更多输入。在 cmd3.pl 中进行多次读取可以正常工作,并且从 STDIN 读取更多项目,但是一旦对 cmd3.pl 的第一次调用完成,STDIN 为空(或处于某种不可用状态)。

当 perl 脚本返回时 STDIN 会发生什么?有没有办法使用 perl 或其他东西来模拟这种输入?

提前致谢。

4

5 回答 5

3

我创造了重现问题的条件,但一切都很顺利:

来电者.pl:

#! /usr/bin/perl

$cmd = "echo 'a\\
b\\
c\\
d\\
e\\
f' | ~/doc/Answers/src/pltest/cmd1.csh";

sub system_tcsh
{
   @args = ("tcsh", "-c", $cmd);
   return system(@args);
}

system_tcsh

cmd1.csh:

#! /bin/csh
echo "${0}(cmd1) $argv[*]"
set line1 = $<
set line2 = $<
set line3 = $<
setenv lineno  3
echo "cmd1: read: $line1 $line2 $line3"
./cmd2.csh

cmd2.csh:

#! /bin/csh
echo "    ${0}(cmd2) $argv[*] now running..."
./cmd3.csh
./cmd3.csh

set line6 = $<

echo "    ${0}: Read: $line6"

cmd3.csh:

#! /bin/csh
# cmd3
set line = $<
echo "        ${0}: Read: '$line'"

试运行:

frayser@gentoo ~/doc/Answers/src/pltest $ ./caller.pl 
/export/home/frayser/doc/Answers/src/pltest/cmd1.csh(cmd1) 
cmd1: read: a b c
    ./cmd2.csh(cmd2)  now running...
        ./cmd3.csh: Read: 'd'
        ./cmd3.csh: Read: 'e'
    ./cmd2.csh: Read: f

也许你可以修改它来重现问题,或者用它来实现你的解决方案。

--

更新
这是一个重现 Perl STDIN 问题的更新,并提供了解决方法。

用 Perl 版本替换 cmd3 会给出报告的有问题的结果:

替换 cmd3.csh 的 cmd3.pl:

#! /usr/bin/perl
# cmd3

$_=<STDIN>;
chomp();
print "        $0: Read: '$_'\n";

结果:使用cmd3.pl。读取“d”后,没有更多输入可用。

./caller.pl    
./cmd1.csh(cmd1) 
cmd1: read: a b c
    ./cmd2.csh(cmd2)  now running...
        ./cmd3.pl: Read: 'd'
        ./cmd3.pl: Read: ''
    ./cmd2.csh: Read: 

为了纠正这种情况,将cmd2更改为仅向cmd3发送 1 行。第(1)命令很容易做到这一点:

#! /bin/csh
echo "    ${0}(cmd2) $argv[*] now running..."
line | ./cmd3.pl
line | ./cmd3.pl

set line6 = $<

echo "    ${0}: Read: $line6"

这是cmd2.csh优化以避免执行line命令的开销。

#! /bin/csh
echo "    ${0}(cmd2) $argv[*] now running..."
set x = $<
echo $x | ./cmd3.pl

set y = $<
echo $y | ./cmd3.pl

set line6 = $<

echo "    ${0}: Read: $line6"

这是更新后的cmd2.csh的输出。现在使用Perl的功能与使用csh 作为最终脚本的功能相同:不会丢失标准输入。

./cmd1.csh(cmd1) 
cmd1: read: a b c
    ./cmd2.csh(cmd2)  now running...
        ./cmd3.pl: Read: 'd'
        ./cmd3.pl: Read: 'e'
    ./cmd2.csh: Read: f
于 2010-09-29T22:23:42.860 回答
1

首先,我会使用Expect来编写输入脚本,它可能更可靠,并且在大多数 Unix 系统上都可用。

除此之外,根据您的 echo 版本,许多允许在带引号的字符串中使用“\n”,尽管这在功能上应该是等效的。就像是:

$cmd = '/bin/echo -e "a\nb\nc\nd\ne\nf" | ~/pltest/cmd1.csh'

(这适用于我的 Linux 机器..man echo看看你的本地版本有什么能力。)

但是,如果不实际查看 Perl 脚本是如何读取输入的,就很难准确猜测输出的去向,这肯定是一个非常复杂的情况。

于 2010-09-28T17:08:45.627 回答
0

这个关于 Greg Bacon 贪婪 perl stdin 的答案似乎是解决这个问题的一种方法。当然,这意味着我必须更改最里面的 perl 脚本,但如果我能获得更改它的许可,这是迄今为止最好的解决方案。它不是最强大的解决方案 - 可能使用 Expect pm 是最好的,但除此之外,这应该可行。

于 2010-10-05T16:06:18.003 回答
0

And what happens if you do:

echo 'a\
b\
c\
d\
e\
f' > tmpfile
cat tmpfile | ~/pltest/cmd1.csh

If that does work you could organise your Perl script around pre-fetching input, saving it to a temp file and then doing the above cat /path/to/tempfile | /path/to/cmd1.csh trick.

于 2010-09-28T03:04:05.357 回答
0

@just jon:不幸的是,expect 对我不可用,我试图用我拥有的工具找到一种方法(因为我们的请求过程并不完全方便)。不过谢谢你的回复!perl 脚本只是在执行一个 或 <>,它们似乎都不能正常工作。

@ user268396:这似乎也不起作用,我想它是功能等效的,因为无论数据来自何处,管道都会强制输入到 STDIN。

@Frayser:您是否尝试过将第三个脚本(cmd3.csh)作为 perl 脚本?这就是我的问题所涉及的。

于 2010-10-04T17:19:47.357 回答