【问题标题】:Perl - How to create commands that users can input in console?Perl - 如何创建用户可以在控制台中输入的命令?
【发布时间】:2013-03-13 05:41:35
【问题描述】:

我刚刚开始使用 Perl,我非常喜欢它。我正在编写一些基本功能,但我真正想做的是使用控制台命令智能地使用这些功能。例如,假设我有一个添加两个数字的函数。我希望能够在控制台中输入“add 2, 4”并读取第一个单词,然后将这两个数字作为参数传递给“add”函数。本质上,我在使用 Perl ^^' 创建一些基本脚本时寻求帮助。

我对如何在 VB 中执行此操作有一些模糊的想法,但是 Perl,我不知道从哪里开始,也不知道哪些函数对我有用。是否有类似 VB.net 的“拆分”功能,您可以将标量的内容分解为数组?例如,有没有一种简单的方法可以在标量中一次分析一个单词,或者遍历标量直到遇到分隔符?

希望您能提供帮助,任何建议都非常感谢!请记住,我不是专家,我几周前就开始使用 Perl,而我只做 VB.net 半年。

谢谢!

编辑:如果您不确定要建议什么,并且您知道任何可能有帮助的简单/直观的资源,我们也将不胜感激。

【问题讨论】:

  • 我衷心推荐“Learning Perl”一书(它是 O'Reilly 系列的一本)。十年或更长时间前,我用它来学习这门语言,发现它很有帮助。此外,Perl programming documentation 应加书签;是你的朋友。
  • 感谢您的快速回复!我想你不知道 Perl 是不是一种变化很快的语言?这就是我对购买编程语言书籍的恐惧,我担心该语言会很快老化。
  • Perl 自 1987 年以来就已经存在,并且不会消失(至少在我们的有生之年)。您日复一日使用的 90% 到 95% 的基本核心元素不会改变。模块来来去去,但核心的东西是相当扎实的。这是一门很棒的语言;我预测你会在学习中获得很多乐趣。
  • 虽然旧的标准书籍很好,但我推荐Modern Perl Book(它是免费的在线和pdf!)。 perl-tutorial.org 上有一个精选的教程列表

标签: perl scripting


【解决方案1】:

编写一个按名称分派给命令的脚本相当容易。这是一个简单的例子:

#!/usr/bin/env perl

use strict;
use warnings;

# take the command name off the @ARGV stack
my $command_name = shift;

# get a reference to the subroutine by name
my $command = __PACKAGE__->can($command_name) || die "Unknown command: $command_name\n";

# execute the command, using the rest of @ARGV as arguments
# and print the return with a trailing newline
print $command->(@ARGV);
print "\n";

sub add {
  my ($x, $y) = @_;
  return $x + $y;
}

sub subtract {
  my ($x, $y) = @_;
  return $x - $y;
}

这个脚本(比如它的名字myscript.pl)可以这样调用

$ ./myscript.pl add 2 3

$ ./myscript.pl subtract 2 3

一旦你玩了一段时间,你可能想更进一步,为这种事情使用一个框架。有几个可用的,例如App::Cmd,或者您可以采用上面显示的逻辑并根据需要进行模块化。

【讨论】:

  • 抱歉,有几部分代码我没看懂...__PACKAGE__->can($command_name)等在做什么? -> 运算符是什么意思?此外,当您打印 $command->(@ARGV) 时。是的,对不起,Perl 的新手^^'
  • __PACKAGE__ 是当前命名空间,can 是一个通用方法,它询问一个类(或包)是否有某个方法或函数,如果有,则返回对它的引用。 -> is the arrow operator 是一个通用的解引用运算符或方法调用运算符。由于$command 是对函数的引用,因此像我一样调用它会使用这些参数执行函数。您可能需要阅读 The Modern Perl Book 以开始使用。
【解决方案2】:

您想要解析命令行参数。 space 用作分隔符,所以只需执行 ./add.pl 2 3 类似这样的操作:

$num1=$ARGV[0];
$num2=$ARGV[1];

print $num1 + $num2;

将打印5

【讨论】:

  • 老兄,太棒了!这不是我想要的,但它可能更有用。非常感谢,也感谢您的快速回复!
  • $num = shift @ARGV 也很好用。它接受下一个参数并将其放入$num。不过不要将两者混为一谈,shift 也会将$ARGV[1] 移动到$ARGV[0]。我个人通常在我的命令行解析函数中使用while(length($arg = shift @ARGV) > 0)——$arg 是下一个参数,检查长度可以让参数为 0 的参数不会停止进程——但是有很多方法......
  • 听起来很有趣,鲍勃。那么它将数组中的所有内容向左移动一并将第零个元素传输到标量变量中?对吗?
  • 没错。同样,请务必检查文档 (shift)。
【解决方案3】:

这是一个简单脚本语言的简短实现。

每条语句只有一行,结构如下:

Statement = [<Var> =] <Command> [<Arg> ...]
# This is a regular grammar, so we don't need a complicated parser.

标记由空格分隔。一个命令可以接受任意数量的参数。这些可以是变量$var 的内容、字符串"foo" 或数字(int 或float)。

由于这些是 Perl 标量,因此字符串和数字之间没有明显的区别。

这是脚本的序言:

#!/usr/bin/perl
use strict;
use warnings;
use 5.010;

strictwarnings 在学习 Perl 时是必不可少的,否则可能会出现太多奇怪的东西。 use 5.010 是最低版本,它还定义了 say 内置函数(类似于 print,但附加了一个换行符)。

现在我们声明两个全局变量:%env 哈希(表或字典)将变量名与其值相关联。 %functions 包含我们的内置函数。这些值是匿名函数。

my %env;

my %functions = (
  add => sub { $_[0] + $_[1] },
  mul => sub { $_[0] * $_[1] },
  say => sub { say $_[0] },
  bye => sub { exit 0 },
);

现在是我们的读取评估循环(默认情况下我们不打印)。 readline 运算符&lt;&gt; 将从指定为第一个命令行参数的文件中读取,如果没有提供文件名,则从 STDIN 中读取。

while (<>) {
  next if /^\s*\#/; # jump comment lines
  # parse the line. We get a destination $var, a $command, and any number of @args
  my ($var, $command, @args) = parse($_);
  # Execute the anonymous sub specified by $command with the @args
  my $value = $functions{ $command }->(@args);
  # Store the return value if a destination $var was specified
  $env{ $var } = $value if defined $var;
}

那是相当微不足道的。现在来一些解析代码。 Perl 使用 =~ 运算符将正则表达式“绑定”到字符串。正则表达式可能看起来像 /foo/m/foo//x 标志允许我们在正则表达式中包含与实际空白不匹配的空白。 /g 标志全局匹配。这也启用了\G 断言。这是最后一场成功的比赛结束的地方。 /c 标志对于 m//gc 样式解析一次使用一个匹配项并防止正则表达式引擎在输出字符串中的位置被重置非常重要。

sub parse {
  my ($line) = @_; # get the $line, which is a argument
  my ($var, $command, @args); # declare variables to be filled

  # Test if this statement has a variable declaration
  if ($line =~ m/\G\s* \$(\w+) \s*=\s* /xgc) {
    $var = $1; # assign first capture if successful
  }

  # Parse the function of this statement.
  if ($line =~ m/\G\s* (\w+) \s*/xgc) {
    $command = $1;
    # Test if the specified function exists in our %functions
    if (not exists $functions{$command}) {
      die "The command $command is not known\n";
    }
  } else {
    die "Command required\n"; # Throw fatal exception on parse error.
  }

  # As long as our matches haven't consumed the whole string...
  while (pos($line) < length($line)) {
    # Try to match variables
    if ($line =~ m/\G \$(\w+) \s*/xgc) {
      die "The variable $1 does not exist\n" if not exists $env{$1};
      push @args, $env{$1};
    }
    # Try to match strings
    elsif ($line =~ m/\G "([^"]+)" \s*/xgc) {
      push @args, $1;
    }
    # Try to match ints or floats
    elsif ($line =~ m/\G (\d+ (?:\.\d+)? ) \s*/xgc) {
      push @args, 0+$1;
    }
    # Throw error if nothing matched
    else {
      die "Didn't understand that line\n";
    }
  }
  # return our -- now filled -- vars.
  return $var, $command, @args;
}

Perl 数组可以像链表一样处理:shift 删除并返回第一个元素(pop 对最后一个元素执行相同的操作)。 push 在末尾添加一个元素,unshift 在开头添加一个元素。

很少的编程语言可以执行简单的程序,例如:

#!my_little_language
$a = mul 2 20
$b = add 0 2
$answer = add $a $b
say $answer
bye

如果 (1) 我们的 perl 脚本保存在 my_little_language,设置为可执行,并且在系统 PATH 中,并且 (2) 我们的小语言中的上述文件保存为 meaning_of_life.mll,并且也设置为是可执行的,那么

$ ./meaning_of_life

应该可以运行。

输出显然是42。请注意,我们的语言还没有字符串操作或对变量的简单赋值。此外,如果能够直接调用具有其他函数返回值的函数,那就太好了。这需要某种括号或优先机制。此外,该语言需要更好的批处理错误报告(它已经支持)。

【讨论】:

  • 哇,好复杂!在你说它相当微不足道之前,我理解了大部分代码 - 我认为正则表达式是我迷路的地方。我知道什么是正则表达式,但我不太擅长使用它们。但真的,非常感谢!
猜你喜欢
  • 2021-09-21
  • 1970-01-01
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-02
  • 2012-12-28
  • 2014-09-07
相关资源
最近更新 更多