基于文件中的条件进行多次替换的sed命令

6

专家们,我有一个文本文件,其中包含一些数学数据,其中有连字符-,我需要将其替换为0,以及数字末尾的MB也需要被删除,这样我就可以得到纯数字。

以下是名为file1的文件中的示例数据:

数据:

$ cat file1

 3708MB 5073MB 5153MB  0MB
 -    63097MB 9939MB  53376MB
 -    817MB   681MB   271MB
 -    2655MB   692MB   2112MB

我尝试过的方法:

$ /bin/sed   's/\r//g; s/-/0/g; s/MB//g' tt4
 3708 5073 5153  0
 0    63097 9939  53376
 0    817   681   271
 0    2655   692   2112

或者可以通过column命令更好地分栏显示它...
$ /bin/sed   's/\r//g; s/-/0/g; s/MB//g' tt4| column -t
3708  5073   5153  0
0     63097  9939  53376
0     817    681   271
0     2655   692   2112

是否有更好的方法来严格确保仅替换连字符-,而不对前后缀中有任何内容的连字符进行替换,并且同样适用于仅在数字末尾删除MB


你的数据中是否真的会出现连字符与前缀和/或后缀一起出现?能给一个例子吗? - Fravadona
通过您发布的示例,您现有的 sed 命令已经生成了您所需的输出。这应该让您知道它不是一个好的示例,因为它没有提供测试所需功能的方法。请编辑您的问题,提供一个可以用来测试建议解决方案是否有效的示例。 - Ed Morton
5个回答

5

使用GNU或BSD sed的-E选项,可以实现您想要的功能:

$ sed -E 's/(^| )-( |$)/\10\2/g; s/([0-9])MB( |$)/\1\2/g' file
 3708 5073 5153  0
 0    63097 9939  53376
 0    817   681   271
 0    2655   692   2112

ED Morton,感谢您的回答+1,您能否解释一下 (^| )-( |$)( |$) 的含义。 - micron cloud
(^| ) 表示“字符串的开始或空格”,而 ( |$) 表示“空格或字符串的结束”(在 sed 中,默认情况下 ,所涉及的"字符串"是输入的一行),因此它会检测是否有一个单独的“-”。 - Ed Morton

5

你需要思考如何独特地捕捉这些模式,以便将其从任何其他模式的外观中隔离出来。

在这里,“-”似乎被空格包围。因此,您可以利用这一点使其与其他带有“-”的文本(例如,text-text)区别开来,使其成为唯一的。

sed 's/ - / 0 /g'

对于模式 MB,您可以确保正在寻找遵循某些数字的模式。


sed -r 's/([0-9]+)MB/\1/g' 

因此,你们可以一起编写:

sed -r 's/ - / 0 /g;s/([0-9]+)MB/\1/g' 


Khaithang,感谢您的回答,对此给予+1的支持,这看起来是一个很有前途的解决方案。 - micron cloud
-r 仅在旧版本的 GNU sed 中启用 EREs。-E 在现代版本的 GNU sed 和 BSD sed 中都执行相同的操作,因此在不同的 sed 变体之间更具可移植性。 - Ed Morton

4

与其他答案类似,但可能更加便携:

sed '
    s/[[:space:]]\{1,\}/  /g
    s/^/ /
    s/$/ /
    s/ - / 0 /g
    s/ \([0-9]\{1,\}\)MB / \1 /g
' tt4 | column -t

我还在MB数字周围添加了空格保护。它们需要至少两个空格字符(每端一个),因此我用更通用的一个替换了\r测试以确保条件。

在行的开头和结尾添加空格意味着不需要使用\|,使用它会导致FreeBSD上的代码出错。


或者可以使用awk(可能更容易阅读):

awk '{
    for (i=1; i<=NF; i++) {
        if ($i=="-") $i=0
        if ($i~/^[0-9]+MB$/) sub("MB","",$i)
    }
    print
}' tt4 | column -t

jhnc,感谢您的回答,并给予了+1的支持,这很好,特别是awk版本。 - micron cloud

3

是的,每个问题都有解决方法。

sed   's/\r//g; s/\b-\b/0/g; s/\([0-9]*\)MB/\1/g' bla.txt | column -t
  1. 使用\b只过滤整个单词,您的情况中是-,请看下面的示例。
    $ echo "bla blablabla" | sed "s/bla/replace/g"
    replace replacereplacereplace
    $ echo "bla blablabla" | sed "s/\bbla\b/replace/g"
    replace blablabla
  1. 使用 \(\) 包裹 [0-9]* 可以正确地匹配数字后面的 MB

因此,

$ cat bla.txt
 3708MB 5073MB 5153MB  0MB
 -    63097MB 9939MB  53376MB
 -    817MB   681MB   271MB
 -    2655MB   692MB   2112MB
$ sed   's/\r//g; s/\b-\b/0/g; s/\([0-9]*\)MB/\1/g' bla.txt | column -t
3708  5073   5153  0
-     63097  9939  53376
-     817    681   271
-     2655   692   2112
$

1
Sparrow,感谢您的回答和+1,但是边界匹配s/\b-\b/0/g在这里不起作用。 - micron cloud
1
\b是一个正则表达式扩展。并非所有的sed都支持它。 - M. Nejat Aydin
1
我正在使用 sed (GNU sed) 4.2.2,我相信它应该支持。 - micron cloud
1
我认为\b-\b会失败。例如:a-b将变成a0b - jhnc

2
使用sed
$ sed -Ez ':a;s/([0-9]+)MB/\1/;s/(\n )-/\10/;ta' input_file
 3708 5073 5153  0
 0    63097 9939  53376
 0    817   681   271
 0    2655   692   2112

仅替换没有前缀和后缀的连字符,对于没有空格跟随或不在行首的连字符则失败。 - jhnc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接