【问题标题】:Perl command to replace the string based on positionPerl命令根据位置替换字符串
【发布时间】:2016-05-13 09:50:52
【问题描述】:

我需要检查第 300 个字符是否为{。如果是,则需要将其替换为 0。还要考虑到{ 之前的 10 位数字,制作一个负十进制数。示例:如果输入为111123456789{,则输出为11-112345678.90

我的样本输入是:

H009704COV2009084    PHD0000001H009700204COV2009084    PROD2015122016010418371304COVH009704COV2009084    PTR0000001H0097002C00000000140000000043610000003408092A0000000068061C0000000000000{0000002939340H0000000537585H0000003476926F0000001218378G0000000040292E0000000016497{0000000000827E0000001880498{9000000320436J000000004391000000001606000000000030000000000128000000000006000000004227000000000000000000000000            00000140              0000000000000{0000000000773B0000000000000{000000000000

这里的第 300 个字符是{。因此,如果我将其替换为 0 并将其转换为负小数,则预期输出将是:

H009704COV2009084    PHD0000001H009700204COV2009084    PROD2015122016010418371304COVH009704COV2009084    PTR0000001H0097002C00000000140000000043610000003408092A0000000068061C0000000000000{0000002939340H0000000537585H0000003476926F0000001218378G0000000040292E0000000016497{0000000000827E000-000188049.809000000320436J000000004391000000001606000000000030000000000128000000000006000000004227000000000000000000000000            00000140              0000000000000{0000000000773B0000000000000{000000000000

我可以使用 sed 命令来做到这一点:

sed -e 's/\ (.\ {1,255\ }\ )\ (.\ {1,34\ }\ )\ (.\ {1,9\ }\ )\ ([^{]*\ ){/\1\2+\3.\40/'

但是当输入文件有大量记录(~80,000)时性能很差。如何将上述 sed 命令转换为 Perl 以获得相同的功能?

【问题讨论】:

  • 您想从终端执行命令吗? “转换为负小数”是什么意思?你将{ 替换为0,然后……让它做什么?
  • 由于我是 Perl 新手,我不确定是否可以使用一行命令来执行上述功能。每当在字符串中找到 { 时,它需要用 0 替换,考虑到 { 之前的 10 位数字,也会产生一个负十进制数。例如:如果输入为 111123456789{,则输出为 11-112345678.90
  • 好的,知道了,但是 -- 上面示例中的逗号确实在 8 和 9 之间 (8.90) 或者可能在 9 之后,89.0
  • 抱歉,逗号不是输入的一部分。输入只是 111123456789{ 预期输出是 11-112345678.90
  • 没关系,这就是我的意思,在输出中。所以它是距离{ 更靠左的一个字符。所以1234{ --> 123.40(和左边十个位置)。我会在一分钟内发布,让我知道它的外观。

标签: perl


【解决方案1】:

一种方法是在 Perl 中使用 substr 函数。它通过偏移量(位置)和长度在另一个字符串中找到一个字符串。它可以选择用另一个参数替换它。它返回寻找的子字符串。

这里需要的转换有点复杂,所以它涉及substr 的多次使用,以及一些计数。 - 需要向左插入 10 个位置,小数点/逗号在左侧两个位置。最后,{ 本身被替换。请注意,第一个字符的位置计数从 0 开始。

要了解其工作原理,请使用评论中的示例,即

111123456789{ --> 11-112345678.90

在这种情况下,{ 位于位置 12。

echo "111123456789{" | perl -pe'
   $x = substr($_, 2, 9); substr($_, 2, 9, "-$x."); substr($_, 14, 1, "0")'

(这需要复制粘贴或在终端的单行输入;为了便于阅读,这里分为两行。)上面的$_ 是 Perl 的“默认”变量,携带当前正在处理的内容,所以这里是输入字符串。这将按照指定打印 11-112345678.90

第一个命令提取位置之间的字符串,其中需要输入 -.,它从位置 12 左侧的 10 个位置开始(所以,在 2),长度为 9。然后子字符串写回那里,现在用-. 填充。最后{0 替换。


subtstr的另一种用法

虽然上面允许更一般的转换,但对于插入字符的确切任务,可以简单地在给定位置添加-.,方法是使用0 作为要替换的子字符串的长度。 {的替换如上。

perl -pe 'substr($_, 2, 0, "-"); substr($_, 12, 0, "."); substr($_, 14, 1, "0")'

这样$_每次都会改变,最后通过-p开关打印出来(见尾)。由于第一次插入添加了一个字符,因此第二次插入需要在字符串下方的某个位置发生。

请注意,这并不是更有效。虽然它避免创建一个新字符串$x,但它会额外更改一次字符串。重写字符串的任何部分,除了精确的字符替换,意味着至少必须保存字符串的其余部分,然后再复制回来。对于较长的字符串,这更昂贵,并且这种方法可能效率较低。但是,除非运行许多这样的操作或在基准测试中,否则这不会引起注意。


要将此应用于实际问题,我们有 299 而不是 12:

perl -pe
   '$x = substr($_, 289, 9); substr($_, 289, 9, "-$x."); substr($_, 301, 1, "0")'
   input_file.txt

上面的第二个例子也可以使用,适当调整数字。

Switches 和特殊变量:

  • -e 表示'...' 后面的内容将由 Perl 作为程序执行

  • -p 循环输入行并在每个输入行上运行'' 中的程序。例如,这些行可能来自一个文件,如果在命令行上给出,它会自动打开并提供给该程序的行。这就像-n 所做的那样,但是-p 在程序处理完该行之后也会打印$_(我们不需要说print

  • $_, "the default input and pattern-searching space," 有当前输入行


这也可以通过正则表达式来完成。见the answer by PerlDuck


注意

上述程序在某种意义上是错误的,因为它们会进行所有处理,更改字符串,即使{ 不是在寻找它的地方,在问题。

相反,我们必须首先检查{ 是否确实在给定位置,然后执行上述操作。这显然很容易添加,但随后一切变得更加笨拙和缓慢。相反,我宁愿推荐一种基于正则表达式的解决方案,例如 the answer by PerlDuck 中的解决方案。

或者也许是一种更快的方法(如该答案下方所述)

pos($string) = 290;
$string =~ s/\G ([0-9]{9}) ([0-9]) \{ /-$1.${2}0/x;

通过首先设置pos\G assertion 将使正则表达式引擎在该位置启动。然后分别匹配 9 位和 1 位数字,后跟{,并根据需要替换它们。如果{ 不存在,则整个匹配失败,字符串保持不变。

【讨论】:

  • @PeterMortensen 非常感谢您清理并因此改进了这个答案,这是我在这里的早期帖子之一。 (这也引起了我对它的注意以及一些更需要的调整。)
【解决方案2】:

据我了解问题和您的输入行,比如$line,这种模式给了我您想要的结果:

$line =~ s/^(.{289})    # Start and then 289 arbitrary chars -> $1
            (\d{9})     # Nine digits                           -> $2
            (\d)        # Another 10th digit                 -> $3
            \{          # Literal '{' at pos. 300
          /${1}-${2}.${3}0/x;

然后替换为前 289 个字符、一个减号、接下来的 9 个数字、一个点、第 10 个数字和一个 0(零)(其余内容保持不变)。

【讨论】:

  • 这里也有一个正则表达式。由于 OP 提到了性能(和一般情况下),最好不要捕获和复制所有 ~300 个字符,而只替换需要的字符,比如使用 pos\G。例如沿着perl -wE '$s = "123abc"; pos($s) = 3; $x =~ s/\G(.)/X/; say $s' 的行,在位置3 后替换一个字符,所以a 变为X\Gpos 匹配(搜索“pos”或转到 perlre 中的“断言”)。我认为您的代码可以直接进入,而不是 3 和 oneliner 中的单个字符捕获和替换(在较短的示例中测试)。
  • @zdim 听起来很合理。我从没想过设置 pos() 但这确实是个好主意。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-11
  • 2014-07-06
  • 1970-01-01
  • 2018-05-02
相关资源
最近更新 更多