简体   繁体   English

仅替换最后一条匹配行(perl单行)

[英]Substitution only on last matching line (perl one-liner)

I have multiple files of the form 我有以下形式的多个文件

version 'aaa'
other 'bbb'
another 'ccc'
version 'ddd'
onemore 'eee'

Some have one version , others have multiple; 有些具有一个version ,另一些具有多个version same with the other keys, but the values never repeat. 与其他键相同,但是值永远不会重复。 I'm using, as part of a bigger bash function, a perl one-liner to modify values 作为更大的bash函数的一部分,我正在使用perl一线式修改值

modify_value() {
  key_to_modify="$1"
  new_value="$2"

  perl -i'' -pe "s|^(\s*)${key_to_modify} .*|\1${key_to_modify} ${new_value}|" "${file}"
}

The indentation on the lines varies and is unpredictable, but should be respected (hence the need for ^(\\s*) ). 行上的缩进是变化的,并且是不可预测的,但是应予以注意(因此需要^(\\s*) )。 This function works great to an extent. 该功能在一定程度上发挥了很大作用。 I can do 我可以

modify_value "onemore" "fff"

And it will be correctly replaced in the text file. 并且它将在文本文件中正确替换。 However, where it breaks down is where I have multiple keys with the same name (such as the aforementioned version ), as this change will be made in all of them. 但是,我要分解的地方是我有多个具有相同名称的键(例如上述version ),因为将对所有这些键进行更改。 In my particular case, I want the modification to be made always in the last case . 在我的特殊情况下, 我希望总是在最后一种情况下进行修改

Since values are never repeated, so far what I have is 由于价值永远不会重复,到目前为止,我拥有的是

modify_value() {
  key_to_modify="$1"
  new_value="$2"

  last_key=$(cat "${file}" | grep "^\s*${key_to_modify}" | tail -1 | perl -pe 's/^\s*//')

  perl -i'' -pe "s|^(\s*)${last_key}|\1${key_to_modify} ${new_value}|" "${file}"
}

This works, but is a bit inelegant. 这行得通,但是有点不雅致。 Would it be possible to leverage the perl one-liner to act only on the latest occurrence of the match, instead? 可以利用perl单一代码只对最近出现的匹配采取行动吗?

You might be tempted to use Tie::File. 您可能会想使用Tie :: File。

# Borodin's solution with the bug fixes I mention below.
perl -MTie::File -e'
   $key  = shift(@ARGV);
   $val  = shift(@ARGV);
   $file = shift(@ARGV);
   tie @f, "Tie::File", $file;
   for (reverse @f) { last if s/^\s*\Q$key\E\s\K.*/$val/; }
' "$1" "$2" "$file"

For small files, Tie::File will provide a solution that's slower than the alternatives and that uses more memory than the alternatives 对于小文件,Tie :: File将提供比替代方案慢的解决方案,并且比替代方案使用更多的内存

For large files, Tie::File will provide an abysmally slow solution to this problem, although it will use less memory than loading the entire file into memory. 对于大文件,尽管Tie :: File使用的内存比将整个文件加载到内存中的内存少,但它会提供一个非常慢的解决方案。

You really can't do any worse than using Tie::File for this problem. 您确实比使用Tie :: File更能解决这个问题。

Here's an alternative: 这是一个替代方案:

perl -i -e'
   $key = shift(@ARGV);
   $val = shift(@ARGV);
   my @f = reverse(<>);
   for (@f) { last if s/^\s*\Q$key\E\s\K.*/$val/; }
   print reverse(@f);
' "$1" "$2" "$file"

You could even avoid the double reversing by having the substitution operator find the last match. 您甚至可以通过让替换运算符找到最后一个匹配项来避免双重反转。

# 5.14+
perl -0777 -i -e'
   $key = shift(@ARGV);
   $val = shift(@ARGV);
   print <> =~ s/\A.*^\s*\Q$key\E\s\K[^\n]*/$val/smr;
' "$1" "$2" "$file"

or 要么

perl -0777 -i -e'
   $key = shift(@ARGV);
   $val = shift(@ARGV);
   $_ = <>;
   s/\A.*^\s*\Q$key\E\s\K[^\n]*/$val/sm;
   print;
' "$1" "$2" "$file"

or 要么

perl -0777 -i -pe'
   BEGIN {
      $key = shift(@ARGV);
      $val = shift(@ARGV);
   }
   s/\A.*^\s*\Q$key\E\s\K[^\n]*/$val/sm;
' "$1" "$2" "$file"

If memory is an issue, reverse the input using File::ReadBackwards (or a similarly efficient tool), change the first match, then reverse the output using File::ReadBackwards. 如果存在内存问题,请使用File :: ReadBackwards(或类似的高效工具)反转输入,更改第一个匹配项,然后使用File :: ReadBackwards反转输出。


These solutions also fix the improper interpolation of $key_to_modify and $new_value into the Perl program (by passing the values as args). 这些解决方案还修复了$key_to_modify$new_value到Perl程序中的不正确插值(通过将值作为args传递)。

These solutions also fix the improper interpolation of $key_to_modify into the regex (by using \\Q ). 这些解决方案还将$key_to_modify的不正确插值修复到正则表达式中(通过使用\\Q )。

I suggest you use Tie::File , which lets you access a file as an array of lines. 我建议您使用Tie::File ,它使您可以按行数组访问文件。 Any modifications made to the array are reflected in the file. 对阵列所做的任何修改都会反映在文件中。 It has been a core module since version 8 of Perl 5, so it shouldn't need to be installed. 自Perl 5版本8以来,它一直是核心模块,因此不需要安装它。

This one-liner works by checking each line of the file from the end to the beginning, and stopping as soon as a match is found. 这种单线工作方式是从头到尾检查文件的每一行,并在找到匹配项后立即停止。 It looks okay, but I'm not in a position to test it at present. 看起来还可以,但是我目前无法对其进行测试。

perl -MTie::File -e"tie @f,'Tie::File',\"${file}\"; s/^\s*${key_to_modify}\s\K.*/${new_value}/ and last for reverse @f"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM