【问题标题】:Using regex to extract a matching pattern from a string and assign it to a variable using perl使用正则表达式从字符串中提取匹配模式并使用 perl 将其分配给变量
【发布时间】:2017-03-09 08:10:03
【问题描述】:

我正在寻求有关提取字符串部分的建议,该部分总是使用 perl 和正则表达式作为括号之间的第一个实例数据出现,并将该值分配给变量。

这是确切的情况,我使用 perl 和正则表达式从大学目录中提取 courseID 并将其分配给变量。请考虑以下几点:

  • BIO-2109-01 (12345) 生物学简介
  • CHM-3501-F2-01 (54321) 化学概论
  • IDS-3250-01 (98765) 美国历史 (1860-2000)
  • SPN-1234-02-F1 (45678) 西班牙历史 (1900-2010)

典型的格式是 [course-section-name] [(courseID)] [courseName]

我的目标是创建一个脚本,它可以一次获取每个条目,将其分配给一个变量,然后使用正则表达式仅提取 courseID 并将 courseID 分配给一个变量。

我的方法是使用搜索和替换来替换与 '' 不匹配的所有内容,然后将剩下的内容(课程 ID)保存到变量中。以下是我尝试过的一些示例:

$string = "BIO-2109-01 (12345) Introduction to Biology";
($courseID = $string) =~ s/[^\d\d\d\d\d]//g;
print $courseID;

结果:21090112345 --- 打印 course-section-name 和 courseID

$string = "BIO-2109-01 (12345) Introduction to Biology";
$($courseID = $string) =~ s/[^\b\(\d{5}\)]\b//g;
print $courseID;

结果:210901(12345) --- 打印 course-section-name、parens 和 courseID

所以我在搜索和替换方面运气不佳 - 但是我找到了这个金块:

\(([^\)]+)\)

http://regexr.com/ 上将匹配括号部分。但是,它也会匹配多个参数,例如 (abc)。

我现在不确定如何做这样的事情:

$string = "BIO-2109-01 (12345) Introduction to Biology";
($courseID = $string) =~ [magicRegex_goes_here];
print courseID;     

结果 12345

或者,更好:

$string = IDS-3250-01 (98765) History of US (1860-2000)
($courseID = $string) =~ [magicRegex_goes_here];
print courseID;

结果 98765

任何建议或指导将不胜感激。我已经尝试了我所知道的一切,并且可以研究正则表达式来解决这个问题。如果有更多信息我可以包括,请询问。

更新

use warnings 'all';
use strict;
use feature 'say';

my $file = './data/enrollment.csv';      #File this script generates
my $course = "";                         #Complete course string [name-of-course] [(courseID)] [course_name]
my @arrayCourses = "";                   #Array of courseIDs
my $i = "";                              #i in for loop
my $courseID = "";                       #Extracted course ID
my $userName = "";                       #Username of person we are enrolling
my $action = "add,";                     #What we are doing to user
my $permission = "teacher,";             #What permissions to assign to user
my $stringToPrint = "";                  #Concatinated string to write to file
my $n = "\n";                            #\n
my $c = ",";                             #,

#BEGIN PROGRAM

print "Enter the username \n";

chomp($userName = <STDIN>);               #Get the enrollee username from user

print "\n";

print "Enter course name and press enter.  Enter 'x' to end. \n";  #prompt for course names

while ($course ne 'x') {
        chomp($course = <STDIN>);
        if ($course ne "x") {
                if (($courseID) = ($course =~ /[^(]+\(([^)]+)\)/) ) {     #nasty regex to extract courseID - thnx PerlDuck and zdim
                        push @arrayCourses, $courseID;                    #put the courseID into array
                }
                else {
                        print "Cannot process last entry check it";
                }
        }
        else {
                last;
        }
}

shift @arrayCourses;                      #Remove first entry from array - add,teacher,,username

open(my $fh,'>', $file);                  #open file

for $i (@arrayCourses)                    #write array to file
{
        $stringToPrint= join "", $action, $permission, $i, $c, $userName, $n ;
        print $fh $stringToPrint;
}

close $fh;

这样就可以了!欢迎提出建议或改进!感谢@PerlDuck 和@zdim

【问题讨论】:

  • +1 用于展示您的尝试!请注意,[…] 表示 字符类,基本上表示 [] 之间的字符中的一个(任意)字符。所以[ab\dL] 匹配其中一个 ab、一个数字或L,而不是全部匹配。
  • 提醒,以防万一出现问题,请参阅:What should I do when someone answers my question?

标签: regex string perl scripting


【解决方案1】:

既然你确定了格式

my ($section, $id, $name) = 
    $string =~ /^\s* ([^(]+) \(\s* ([^)]+) \)\s* (.+) $/x;

这里的关键是否定字符类[^...],它匹配除^ 后面列出的字符之外的任何一个字符(这使其成为“否定”)。未转义的括号捕获匹配,除非在字符类[] 中,它们被视为文字。

它首先匹配除( 之外的所有连续字符,因此直到第一个(,它周围的一对( ) 捕获的内容。然后除) 之外的所有其他内容,直到第一个结束括号,也被它自己的一对( ) 捕获。这在文字括号 \( ... \) 之间,它们在 ( ) 之外,因为我们不希望它们被捕获。然后捕获所有其余部分,(.+),至少需要一些字符,因为+ 表示 一个 或更多。请注意,这些可以是空格。我们通过在捕获括号之前专门匹配它,从第一次捕获中排除可能的前导空白,并在 id 括号周围提取(一些)可能的空格。

/x 修饰符允许在内部使用空格(以及 cmets 和换行符),这有助于提高可读性。 match 运算符返回所有匹配项的列表,我们将其分配给变量。请注意,即使只有一个匹配项,它仍然会返回(它作为)一个列表。见Regular Expressions Tutorial (perlretut)

然后,假设您在文件中有目录

use warnings 'all';
use strict;
use feature 'say';

my $file = 'catalog.txt';

open my $fh, '<', $file or die "Can't open $file: $!";

while (my $line = <$fh>) 
{
    next if $line =~ /^\s*$/;  # skip empty lines

    # Strip leading and trailing white space
    $line =~ s{^\s*|\s*$}{}g;

    my ($section, $id, $name) = 
        $line =~ /^ ([^(]+) \(\s* ([^)]+) \)\s* (.+) $/x
            or do {
                warn "Error with expected format -- ";
                next;
            };

    say "$section, $id, $name";
}
close $fh;

我使用 s{}{} 分隔符,因为 s/// 将标记的语法高亮与此模式混淆,这也是一个很好的演示,因为这些有时有助于提高可读性。

您可以将检索到的变量存储在合适的数据结构中。数组和散列(及其引用)的任何组合都会浮现在脑海中,这取决于以后需要对它们做什么。见Cookbook of Data Structures (perldsc)

注意错误处理。由于没有任何匹配涉及*(允许 匹配 - 没有),如果您的格式的任何组件与预期不符,则根本不会匹配,我们会收到错误. .+ 非常宽松,但它仍然需要 something 存在。这就是为什么首先去除尾随空格,以便最后一个模式(.+) 不能仅由空格来满足。

如果唯一的目标课程ID并且我们确定第一个括号在它周围

my ($id) = $line =~ / \(\s* ([^)]+) \) /x  or do { ... };

我们现在只需要匹配和捕获中间部分,括号内的东西。

【讨论】:

  • @ikegami 是的,谢谢——只是编辑、清理和添加错误检查等。感谢您的编辑。
  • 谢谢!我从您的帖子中窃取了文件处理和打开文件的想法。在我的代码中,我写入文件,但我不知道如何在 perl 中打开文件。另外,我还没有坐下来完全阅读您的正则表达式。我需要编写这个脚本,但我打算完成。正则表达式是我真正想要扎实的东西。感谢您的回复@zdim
  • @squadguy 太好了,我很高兴它在正则表达式之外很有用。这确实是处理文件的基本方式,可以满足大部分需求。但是你想了解基础知识。一组来源是 Perl 的文档,至于文件,它首先是 perlopentut。另一本是你最喜欢的书。正则表达式也是如此。仔细阅读基础知识,您将立即拥有一个非常有用的工具。在基础知识之后, 更容易继续学习。
  • @squadguy 我在正则表达式解释中添加了一点,可能值得重读。
  • @ikegami 感谢您的进一步cmets!我希望说清楚,虽然我很犹豫在这里发布一些可能允许可疑数据通过的东西。我宁愿在另一边犯错。但是,您是对的,还有其他方法。它确实需要更多,并且需要一些编辑。谢谢。
【解决方案2】:
#!/usr/bin/env perl

use strict;
use warnings;

while( my $line = <DATA> ) {
    if (my ($courseID) = ($line =~ /[^(]+\(([^)]+)\)/) ) {
        print "course-ID = $courseID; -- line was $line";
    }
}

__DATA__
BIO-2109-01 (12345) Introduction to Biology
CHM-3501-F2-01 (54321) Introduction to Chemistry
IDS-3250-01 (98765) History of US (1860-2000)
SPN-1234-02-F1 (45678) Spanish History (1900-2010)

输出:

course-ID = 12345; -- line was BIO-2109-01 (12345) Introduction to Biology
course-ID = 54321; -- line was CHM-3501-F2-01 (54321) Introduction to Chemistry
course-ID = 98765; -- line was IDS-3250-01 (98765) History of US (1860-2000)
course-ID = 45678; -- line was SPN-1234-02-F1 (45678) Spanish History (1900-2010)

我使用的模式/[^(]+\(([^)]+)\)/也可以写成

/ [^(]+     # 1 or more characters that are not a '('
  \(        # a literal '('. You must escape that because you don't want
            # to start it a capture group.
  ([^)]+)   # 1 or more chars that are not a ')'.
            # The sorrounding '(' and ')' capture this match
  \)        # a literal ')'
/x

/x 修饰符允许您在模式中插入空格、cmets 甚至换行符。


以防万一您不确定/x。你确实可以写:

while( my $line = <DATA> ) {
    if (my ($courseID) = ($line =~ / [^(]+   # …
                                     \(      # …
                                     ([^)]+) # …
                                     \)      # …
                                    /x ) ) {
        print "course-ID = $courseID; -- line was $line";
    }
}

这可能不好读,但您也可以将正则表达式存储在单独的变量中:

my $pattern = 
    qr/ [^(]+     # 1 or more characters that are not a '('
        \(        # a literal '(' (you must escape it)
        ([^)]+)   # 1 or more chars that are not a ')'.
                  # The sorrounding '(' and ')' capture this match
        \)        # a literal ')'
      /x;

然后:

if (my ($courseID) = ($line =~ $pattern)) {
    …
}

【讨论】:

  • 我偷了你的部分 while 循环和你提供的正则表达式并将它们包含在我的代码中。我还没有真正坐下来消化你的正则表达式,但它在我的候选名单上。我计划学习 perl 的文本处理能力。非常感谢您的回复 - 我无法构建该正则表达式。
猜你喜欢
  • 2013-01-20
  • 2014-07-20
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
  • 1970-01-01
  • 2021-06-11
  • 2022-10-01
  • 1970-01-01
相关资源
最近更新 更多