仅在最后一条匹配线上替换(perl one-liner)

时间:2015-01-26 02:04:34

标签: macos bash perl

我有多个表格

的文件
version 'aaa'
other 'bbb'
another 'ccc'
version 'ddd'
onemore 'eee'

有些人有一个version,有些人有多个;与其他键相同,但值不会重复。作为更大的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}"
}

线条上的缩进变化且不可预测,但应该得到尊重(因此需要^(\s*))。此功能在一定程度上起作用。我能做到

modify_value "onemore" "fff"

它将在文本文件中正确替换。但是,它崩溃的地方是我有多个具有相同名称的键(例如前面提到的version),因为这些更改将在所有这些中进行。在我的特定情况下,我希望在最后一种情况下总是进行修改

由于价值从不重复,到目前为止我所拥有的是

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}"
}

这有效,但有点不优雅。是否有可能利用perl one-liner仅对匹配的最新事件采取行动呢?

2 个答案:

答案 0 :(得分:2)

你可能想要使用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"

对于小文件,Tie :: File将提供比替代方案慢的解决方案,并且使用比替代方案更多的内存

对于大文件,Tie :: File将为这个问题提供极其缓慢的解决方案,尽管它会比将整个文件加载到内存中使用更少的内存。

你真的不能比使用Tie :: File来解决这个问题。

这是另一种选择:

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"

您甚至可以通过让替换运算符找到最后一个匹配来避免双重反转。

# 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"

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"

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"

如果内存有问题,请使用File :: ReadBackwards(或类似的高效工具)反转输入,更改第一个匹配,然后使用File :: ReadBackwards反转输出。


这些解决方案还修复了$key_to_modify$new_value对Perl程序的不正确插值(通过将值作为args传递)。

这些解决方案还修复了$key_to_modify对正则表达式的不正确插值(使用\Q)。

答案 1 :(得分:0)

我建议您使用Tie::File,它允许您以行数组的形式访问文件。对阵列所做的任何修改都会反映在文件中。它是Perl 5版本8以来的核心模块,因此不需要安装。

此单线程通过从结尾到开头检查文件的每一行来工作,并在找到匹配后立即停止。它看起来没问题,但我目前无法测试它。

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