提问人:santy 提问时间:7/29/2023 最后编辑:brian d foysanty 更新时间:7/31/2023 访问量:154
如何替换字符串中的多行?
How can I replace multiple lines in a string?
问:
我想使用 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
但是,上述方法没有奏效。有什么比这更简单的方法呢?
答:
如果你在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 转义正则表达式特殊字符(至少有和 parens),然后将单词与 连接起来,然后在每个这样的字符串前面添加字符串(空格或制表符用于那些前导空格,但不是换行符,因此不能使用);然后将它们全部加入 .完全$
'\s+'
'[ \t]+'
\s+
'\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 $_
s/$patt/$repl/g;
print;
}
这是通过在命令行上向其传递文件名来运行的。它应该对空白细节更加可靠,即使它不再那么简单了。
通过设置就地编辑 () like command-lline 选项,并取消设置输入记录分隔符 () 来处理文件,以便将文件一次性“啜饮”到标量中。在这里,我们使用 <>
运算符读取它。默认情况下,这被分配给 $_
变量,默认情况下正则表达式绑定到该变量。进入文件,通过(没有任何东西打印到屏幕上)。$^I
-i
$/
print
$^I
评论
export v1="copy-here"
如果您已经在 :$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)
评论
示例文件:
$ 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)
注意:假设 in 中的条目与 in 中的行相同匹配(以在字段之间包含相同数量的空格),否则我们需要添加更多代码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
评论
old_string
old_string
file.txt
tmp.txt
mv tmp.txt file.txt
GNU awk
awk
inplace
tmp.txt
)
awk -v mystring="connect_supply_net" '
$0 ~ mystring{
printf "if { [llength %s] > 0 } {\n %s \n} \n", $NF, $0; next }
{print}
' file
评论
mystring="connect_supply_net"
match()
$0 ~ mystring
使用 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 编写的语法:
使用 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 有 (row-separator) 和 (output row-separator) 内置变量,将它们设置为在文件中从不匹配的模式,并将空字符串设置为一行行为,例如 字节(与上述示例相同)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 中)
评论
old_string="connect ..."
不会像你想象的那样工作;因为你已经用双引号将字符串括起来,所以组件将被替换为名为 的变量的内容,如果变量未定义,则将保存在变量中,作为 ;任务也是如此;在这种情况下,您希望将两组字符串括在单引号中,以便赋值将引用视为文本后跟字符串$port
bash
port
port
$port(VSSE)
(VSSE)
new_string
$port
$
port
connect_supply_net
connect_suppy_net
sed
sed 's/^\([[:space:]]*\)connect_supply_net .*port(\([^)]\+\))[[:space:]]*/\1if { [llength $port(\2)] > 0 } {\n\1 &\n\1}/'