如何更改 VIM sed命令以在 bash 脚本中工作?

How do i change a VIM sedcommand to work in a bash script?

提问人:Ron 提问时间:11/9/2023 最后编辑:romainlRon 更新时间:11/10/2023 访问量:115

问:

我在 Vim 中有一个非常有用的 sed 命令,我想把它放在一个简单的 bashs cript 中。

但是我已经尝试了它的许多变体(疯狂地谷歌),但它不起作用......

在 Vim 中工作的 sed 字符串是:

%s/\( \)[0-9]\{8\}$/\=printf(' %08d', line('.'))/g

我试过了

sed -i "s/\( \)[0-9]\{8\}$/\=printf(' %08d', line('.'))/g"

和几种变体。

我想更改一行中的最后 8 位数字(行 van 的长度不同,但它们总是以空格和 8 位数字结尾。有时我需要删除和删除行,因此需要更改序列号(行号):

此外,我使用相同的 Vim sed 命令来更改单词,这样我就可以改用行号。例:

%s/LINENUMB/\=printf('%08d', line('.'))/g

在这里,我想用实际的 8 位行号更改单词。LINENUMB

它可以在 Vim 中工作,但我无法让它从终端或脚本中工作。

示例 1,我删除了 .现在我需要更改序列号:00000002

321321321321321321321321321321321321321321321321321321 00000001
6546546546546546546546546546546546546546546546546546546546546546 00000003
987987987987987987987987987987987987987987987987987987987 00000004

预期成果:

321321321321321321321321321321321321321321321321321321 00000001
6546546546546546546546546546546546546546546546546546546546546546 00000002
987987987987987987987987987987987987987987987987987987987 00000003

示例 2,我想更改为实际行号。假设它在文件中是行的:LINENUMB00206206

987654321LINENUMB123456789

预期成果:

98765432100206206123456789
bash awk sed vim printf

评论

3赞 phd 11/9/2023
一些小问题:在 Vim 中不是“sed”,而是“替代”。 非常特定于 Vim,它不能在 Vim 之外使用。:s=printf()
0赞 markp-fuso 11/9/2023
在第一个例子中......序列号是否始终与行号相同;换句话说,文件的第一行是否总是有一个 ?00000001
0赞 markp-fuso 11/9/2023
这两种情况都可以发生在同一个文件中吗,即一行是否可以在行的最后一个字段中具有字符串和 8 位序列号?LINENUMB
0赞 stevesliva 11/9/2023
@phd,printf 是一个内置的 shell,其工作方式类似。line(),OTOH,我认为您需要在 shell 实现中手动跟踪
1赞 phd 11/9/2023
@stevesliva 在 shell 中有效,但在 中不起作用,表达式是特定于 Vim 的。printfs//\=printf()/sed\=

答:

3赞 markp-fuso 11/9/2023 #1

假设:

  • 一个文件可以包含这两种情况(最后一个字段中的 8 位序列号;行中的文本字符串)LINENUMB
  • 如果在一行中出现多次,则每次出现都将替换为相同的行号LINENUMB
  • 文件的第一行始终以 sequence 开头00000001

示例数据:

$ cat raw.dat
321321321321321321321321321321321321321321321321321321 00000001
654654654XLINENUMBX546546546546546546546546546546546546546546546 00000003
          ^^^^^^^^                                               ^^^^^^^^
98798XLINENUMBX9879879879879879XLINENUMBX7987987987987987 00000004
      ^^^^^^^^                  ^^^^^^^^                  ^^^^^^^^

注意:文件不包含以下行;提供这些线条是为了直观地突出显示感兴趣的字符串^^^^^

一个想法:awk

awk '
             { lineno = sprintf("%08d",FNR) }           # zero-pad current line number (FNR)
/LINENUMB/   { gsub(/LINENUMB/,lineno) }                # replace all strings "LINENUMB" with the zero-padded line number
$NF+0 != FNR { $NF = lineno }                           # if last field is not same as current line number then update the field with the zero-padded line number
1                                                       # print current line to stdout
' raw.dat

这将产生:

321321321321321321321321321321321321321321321321321321 00000001
654654654X00000002X546546546546546546546546546546546546546546546 00000002
          ^^^^^^^^                                               ^^^^^^^^
98798X00000003X9879879879879879X00000003X7987987987987987 00000003
      ^^^^^^^^                  ^^^^^^^^                  ^^^^^^^^

验证输出正确后,有几种方法可以修改代码以更新原始文件:

  • 将输出重定向到临时/暂存文件(例如,),然后tempfilemv tempfile raw.dat

  • 如果 OP 在支持下使用,则可以按如下方式修改代码:GNU awk-i inplaceawk -i inplace '{ lineno = sprintf("%08d",NR) } ...' raw.dat

评论

1赞 Ron 11/9/2023
谢谢。很好。我明天也会尝试一下,看看什么最适合我的问题。亲切问候。
0赞 Ed Morton 11/9/2023
你不需要第一个,因为它要测试同一个正则表达式两次,你所需要的只是./LINENUMB/ /LINENUMB/ { gsub(/LINENUMB/,lineno) }{ gsub(/LINENUMB/,lineno) }
2赞 Ed Morton 11/9/2023
您可能应该更改为匹配您拥有的,以防万一 OP 一次在多个文件上运行它。sprintf("%08d",NR)sprintf("%08d",FNR)FNR$NF+0 != FNR
0赞 markp-fuso 11/9/2023
@EdMorton是的,我在 NR 和 FNR 之间来回切换......单个文件与多个文件...都回到了我原来的......FNR;谢谢
1赞 Ron 11/9/2023
实际上,同时使用多个文件的想法是我的下一步。感谢您的见解。
1赞 stevesliva 11/9/2023 #2

perl -pe cmd等效于 。它可以从管道或文件中获取 stdin 作为输入。sed cmd

Perl,将最后八位数字替换为行号:

perl -pe 's/\d{8}$/sprintf("%08d",$.)/e' file

Perl,将 LINENUMB 替换为 8 位行号:

perl -pe 's/LINENUMB/sprintf("%08d",$.)/ge' file
  • 这两个替换可以组合成一个 perl 调用,与 sed 相同,两者之间有多个或分号。-e
  • $.是“行号”的 perlvar。
  • s///e表示您在替换字符串的替换端执行 commmand。
  • sprintf这里使用 Perl 中的 VS,因为我们要打印到字符串而不是直接打印到输出流。printf

评论

0赞 Ron 11/9/2023
谢谢你。我认为这是我真正需要的。那么需要学习一些perl。亲切问候。/R
0赞 stevesliva 11/9/2023
或。如果你想要类似 shell 或类似 C 的东西来扩展你对 sed 和 grep 的了解,那么 Perl 是很好的。OTOH:我仍然认为运行 vim headless 是最快的方法。Perl 简直是最简洁的。
0赞 Ron 11/9/2023
今天经过一些实验室之后,实际上是赢家...... :-)我用它来解决我的问题。perl -pe 's/( )\d{8}$/sprintf(“ %08d”,$.)/e' $1 > step1.txt perl -pe 's/LINENUMB/sprintf(“%08d”,$.)/ge' 步骤 1.txt > 步骤 2.txt
0赞 stevesliva 11/10/2023
可以组合成一个步骤。
2赞 Daweo 11/9/2023 #3

我会利用 GNU 来完成这项任务,让内容成为AWKfile.txt

ABLE 00000001
CHARLIE 00000003
DOG 00000004
ZEBRA 00000026
STUFFLINENUMB 00000027

然后

awk '{$NF=sprintf("%08d",NR);gsub(/LINENUMB/,$NF);print}' file.txt

给出输出

ABLE 00000001
CHARLIE 00000002
DOG 00000003
ZEBRA 00000004
STUFF00000005 00000005

Explantion:我使用 sprintf 函数将当前行 () 的编号转换为宽度为 8 或更大的前导零的字符串,然后将其分配给最后一个字段 (),将所有替换为最后一个字段和该行的内容。免责声明:我假设您的文件具有使用单空格字符分隔的字段。如果您想了解更多关于或阅读 8 个强大的 Awk 内置变量 – FS、OFS、RS、ORS、NR、NF、FILENAME、FNRNRNFLINENUMBprintNFNR

(在 GNU Awk 5.1.0 中测试)

评论

0赞 Ron 11/9/2023
谢谢。非常整洁。就我而言,LINENUMB 行的末尾永远不会有序列号。LINENUMB 仅在标题和预告片中出现。捆绑包和文件拖车之类的。谢谢。
0赞 potong 11/10/2023 #4

这些可能对你有用(GNU sed):

第一种情况:

 sed = file | sed -E 'N;s/(.*)\n(.*) .*/\2 00000000\1/;s/(.* ).*(.{8})$/\1\2/'

在每行之前插入一行包含行号的行。

将生成的文件通过管道传递到另一个 sed 调用中。

在第二个 sed 调用中,获取两行并使用模式匹配将表示当前行号的字段替换为前面加上 8 个零的新行号。

将新行号字段减少到 8 位数字。

第二种情况:

sed  '/LINENUMB/{p;=;d}' file |
sed -E '/LINENUMB/{N;s/\n/&00000000/;s/LINENUMB(.*)\n.*(.{8})$/\2\1/}'

使用与上述相同的技术,但仅适用于包含 的行。LINENUMB

注意第二种解决方案假定每行仅更换一次。LINENUMB

评论

0赞 Ron 11/11/2023
谢谢。这个线程中有许多很酷的解决方案。
0赞 stevesliva 11/14/2023
人,是有限的。 是内联替代项。sed =grep -n ''