使用 sed/awk/perl 替换多行字符串

问题描述 投票:0回答:7

我想使用 sed/awk/perl 替换多行字符串,如下所示

    #-------------------------------------------------------------------------------
    # supply connections
    #-------------------------------------------------------------------------------
    
        connect_supply_net VSS      -ports $port(VSSE)
        connect_supply_net VDDS_CPU -ports $port(VDDP)
        connect_supply_net VDD_CPU  -ports $port(VDDPE)

#-------------------------------------------------------------------------------
# Update states
#-------------------------------------------------------------------------------

上面是输入文件,我想在输出文件中将上面 3 行替换为如下

    #-------------------------------------------------------------------------------
    # supply connections
    #-------------------------------------------------------------------------------
 
    if { [llength $port(VSSE)] > 0 } {
      connect_supply_net VSS -ports $port(VSSE)
    }
    if { [llength $port(VDDP)] > 0 } {
      connect_supply_net VDDS_CPU -ports $port(VDDP)
    }
    if { [llength $port(VDDPE)] > 0 } {
      connect_supply_net VDD_CPU -ports $port(VDDPE)
    }

    #-------------------------------------------------------------------------------
    # Update states
    #-------------------------------------------------------------------------------

有什么更简单的方法可以做到这一点?

我尝试使用传递变量,然后使用 sed 命令,但它不起作用。我尝试如下 -

old_string="connect_supply_net VSS      -ports $port(VSSE)
connect_supply_net VDDS_CPU -ports $port(VDDP)
connect_supply_net VDD_CPU  -ports $port(VDDPE)"

new_string="if { [llength $port(VSSE)] > 0 } {
  connect_supply_net VSS -ports $port(VSSE)
}
if { [llength $port(VDDP)] > 0 } {
  connect_supply_net VDDS_CPU -ports $port(VDDP)
}
if { [llength $port(VDDPE)] > 0 } {
  connect_supply_net VDD_CPU -ports $port(VDDPE)
}"

sed -i ":a;N;$!ba;s/${old_string}/${new_string}/g" file.txt

但是,上面的方法并没有起作用。有什么更简单的方法可以做到这一点?

string perl awk sed multiline
7个回答
2
投票

示例文件:

$ cat file.txt
some line
connect_supply_net VSS      -ports $port(VSSE)
another line
connect_supply_net VDDS_CPU -ports $port(VDDP)
yet another line
connect_supply_net VDD_CPU  -ports $port(VDDPE)
one more line
connect_supply_net ABCD  -ports $port(XYZ)
last line

$ cat replace.txt
connect_supply_net VSS      -ports $port(VSSE)
connect_supply_net VDDS_CPU -ports $port(VDDP)
connect_supply_net VDD_CPU  -ports $port(VDDPE)

注意: 假设

replace.txt
中的条目与
file.txt
中的行相同匹配(以在字段之间包含相同数量的空白),否则我们需要添加更多代码

一个

awk
想法:

awk '
FNR == NR     { replace[$0]; next }
$0 in replace { $3 = $3                          # squeeze multiple white spaces into single spaces throughout entire line
                $0 = "if { [llength " $4 "] > 0 } {" ORS "  " $0 ORS "}"
              }
1
' replace.txt file.txt

注意: 如果 OP 必须使用

old_string
变量,则将
replace.txt
替换为
<(echo "${old_string}")

这会生成:

some line
if { [llength $port(VSSE)] > 0 } {
  connect_supply_net VSS -ports $port(VSSE)
}
another line
if { [llength $port(VDDP)] > 0 } {
  connect_supply_net VDDS_CPU -ports $port(VDDP)
}
yet another line
if { [llength $port(VDDPE)] > 0 } {
  connect_supply_net VDD_CPU -ports $port(VDDPE)
}
one more line
connect_supply_net ABCD  -ports $port(XYZ)
last line

1
投票

如果您已经有

$old_string
中的行:

new_string=$(awk '{ printf "if { [llength %s] > 0 } {\n  %s\n}\n", $NF, $0 }' <<< $old_string)

上面的方法可行,但是如果我们将脚本放在单独的文件中

# script.awk
{
    printf "if { [llength %s] > 0 } {\n", $NF
    print " " $0
    print "}"
}

那么命令会更清晰:

new_string=$(awk -f script.awk <<< $old_string)

1
投票

如果您可以在 shell 中定义这些变量,那么您需要以某种方式使它们可供使用它们的程序使用,并且该程序需要将整个文件读入一个变量,以便能够找到这些多变量- 线条图案。使用 Perl

$ export old_string="..."
$ export new_string="..."

$ perl -0777 -i -wnE's/$ENV{old_string}/$ENV{new_string}/g; print' filename

上面的方法在我的测试中有效,但是在终端上复制多行文本块非常挑剔并且对空白细节敏感。那些标签在那里吗?到底有多少个前导空格?相反,此文本在脚本中更容易控制。

我们可以输入要匹配的确切文本,并通过复制粘贴进行替换,也许最好使用 here-doc 引用。然而,空白问题仍然存在——对齐是用制表符完成的吗?复制粘贴会将它们丢失为空格。再说一遍,我粘贴这些空格对吗?

相反,为该文本形成正则表达式模式,以处理各种空白。一种方法:

复制行并处理每一行 - 分解为单词,在每个单词中使用

quotemeta
转义正则表达式特殊字符(至少 $ 和括号),然后使用
'\s+'
连接单词,并在前面添加
'[ \t]+'
字符串每个这样的字符串(那些前导空格的空格或制表符,但不是换行符);然后用
'\n'
将它们全部加入。总共

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

die "Usage: $0 filename\n" if not @ARGV;

my $patt = 
    join '\n',  
    map { '[ \t]+' . join '\s+', map { quotemeta } split }
        'connect_supply_net VSS      -ports $port(VSSE)',
        'connect_supply_net VDDS_CPU -ports $port(VDDP)',
        'connect_supply_net VDD_CPU  -ports $port(VDDPE)';

my $repl = <<'EOS';
    if { [llength $port(VSSE)] > 0 } {
      connect_supply_net VSS -ports $port(VSSE)
    }
    if { [llength $port(VDDP)] > 0 } {
      connect_supply_net VDDS_CPU -ports $port(VDDP)
    }
    if { [llength $port(VDDPE)] > 0 } {
      connect_supply_net VDD_CPU -ports $port(VDDPE)
    }
EOS

local $^I = '.bak';  # edit "in-place" -- change the file. keep backup

local $/;            # read the file all at once

while (<>) {  # whole file read into default variable $_
    s/$patt/$repl/g;
    print;
}

这是通过在命令行上传递文件名来运行的。它对于空白细节应该更加稳健,尽管它不再那么简单了。

通过设置就地编辑(如

-i
命令行选项)来处理文件,并取消设置输入记录分隔符,以便文件一次“slurped”为标量。这里我们用
<>
运算符
来读取它。默认情况下,它被分配给
$_
变量
,正则表达式绑定到该变量(默认情况下)。然后
print
转到文件,按
$^I
(屏幕上不会打印任何内容)。


0
投票
awk -v mystring="connect_supply_net" '
    match($0,mystring){
    printf "if { [llength %s] > 0 } {\n %s \n} \n", $NF, $0; next }
    {print}
' file

0
投票

本质上,您不能使用

sed
来实现此目的,
perl
克服了尝试使用 sed 执行此操作的所有潜在问题。

就你的例子而言,你本质上是想转向

connect_supply_net VSS      -ports $port(VSSE)
connect_supply_net VDDS_CPU -ports $port(VDDP)
connect_supply_net VDD_CPU  -ports $port(VDDPE)

进入模式

^\s*connect_supply_net VSS      -ports \$port\(VSSE\)\s*
^\s*connect_supply_net VDDS_CPU -ports \$port\(VDDP\)\s*
^\s*connect_supply_net VDD_CPU  -ports \$port\(VDDPE\)\s*

(注意:所有正则表达式元字符都需要转义。)

这里尝试使用 Perl 来做到这一点:

export old_string='
connect_supply_net VSS      -ports $port(VSSE)
connect_supply_net VDDS_CPU -ports $port(VDDP)
connect_supply_net VDD_CPU  -ports $port(VDDPE)
'

export new_string='
if { [llength $port(VSSE)] > 0 } {
  connect_supply_net VSS -ports $port(VSSE)
}
if { [llength $port(VDDP)] > 0 } {
  connect_supply_net VDDS_CPU -ports $port(VDDP)
}
if { [llength $port(VDDPE)] > 0 } {
  connect_supply_net VDD_CPU -ports $port(VDDPE)
}
'

perl -0777 -pe '
BEGIN {
   s/^\n//, s/(?<=\n)\n$// for ($ENV{old_string}, $ENV{new_string});
   $old_string = join "", (map { sprintf "(^\\s*)%s\\s*\n", quotemeta } split "\n", $ENV{old_string});
}

s/$old_string/(join "", map { sprintf "%s%s", $1, $_ } split "\n", $ENV{new_string}) . "\n"/me
' file.txt

输出:

    #-------------------------------------------------------------------------------
    # supply connections
    #-------------------------------------------------------------------------------
    
        if { [llength $port(VSSE)] > 0 } {    
          connect_supply_net VSS -ports $port(VSSE)    
        }    
        if { [llength $port(VDDP)] > 0 } {    
          connect_supply_net VDDS_CPU -ports $port(VDDP)    
        }    
        if { [llength $port(VDDPE)] > 0 } {    
          connect_supply_net VDD_CPU -ports $port(VDDPE)    
        }
#-------------------------------------------------------------------------------
# Update states
#-------------------------------------------------------------------------------

从文件中读取输入/输出字符串通常更简单,它避免了尝试定义 shell 变量时潜在的字符串引用问题。


0
投票

使用 TXR 我们可以修复所有

connect_supply_net
命令以具有长度保护,并将
if
语句放在与原始语句相同的缩进处。

我们还可以修复“更新状态”块注释的错误缩进。

$ txr fix.txr data
    #-------------------------------------------------------------------------------
    # supply connections
    #-------------------------------------------------------------------------------

        if { [llength $port(VSSE)] > 0 } {
            connect_supply_net VSS -ports $port(VSSE)
        }
        if { [llength $port(VDDP)] > 0 } {
            connect_supply_net VDDS_CPU -ports $port(VDDP)
        }
        if { [llength $port(VDDPE)] > 0 } {
            connect_supply_net VDD_CPU -ports $port(VDDPE)
        }

    #-------------------------------------------------------------------------------
    # Update states
    #-------------------------------------------------------------------------------

代码:

@(repeat)
@  (cases)
@{indent}connect_supply_net @arg -ports $port(@port)
@    (output)
@{indent}if { [llength $port(@port)] > 0 } {
@{indent}    connect_supply_net @arg -ports $port(@port)
@{indent}}
@    (end)
@  (or)
#---@dashes
# Update states
#---@dashes
@    (output)
    #---@dashes
    # Update states
    #---@dashes
@    (end)
@  (or)
@line
@    (do (put-line line))
@  (end)
@(end)

Vim 着色的语法:


0
投票

使用 sed/awk/perl 替换多行字符串

这些默认情况下是逐行工作的,但是它们具有允许您解决这个问题的功能。

出于演示目的,让

file.txt
内容为

Able
Baker
Charlie
Dog

期望的输出是

Able
Charlie
Baker
Dog

GNU

sed
-z
选项,可以实现

将输入视为一组行,每行以零字节终止( ASCII ‘NUL’ 字符)而不是换行符。

因此,如果您的文件不包含

\000
字节,它将被视为一行,因此命令将是

sed -z 's|Baker\nCharlie|Charlie\nBaker|' file.txt

GNU

AWK
RS
(行分隔符)和
ORS
(输出行分隔符)内置变量,将它们设置为在文件中从不匹配的模式和空字符串以将所有内容作为一行行为,例如
\000
字节(与上面
sed
示例相同)

awk 'BEGIN{RS="\000";ORS=""}{sub(/Baker\nCharlie/,"Charlie\nBaker");print}' file.txt

perl
运动
-0777
所使用的吸食模式,并且可以与
sed
所使用的
-p -e
模式一起使用,观察语法与
sed
示例中的语法相同

perl -0777 -p -e 's|Baker\nCharlie|Charlie\nBaker|' file.txt

(在 GNU sed 4.8、GNU Awk 5.1.0、perl 5、版本 34、subversion 0 中测试)

© www.soinside.com 2019 - 2024. All rights reserved.