使用sed在两个字符串之间查找和替换字符

3
我有一个用管道符分隔的文件,其中一个列中的某些值/记录包含在值本身中的管道符,使它看起来像比实际列数更多 - 注意"第8列"(加粗)中间有管道符。这实际上应该显示为"|col u lm n8|",其中管道符的位置应替换为空格。
column1|column2|column3|column4|column5|column6|column7|**col|u|lm|n8**|2016|column10|column11|column12|column13|column14|

我需要用空格替换列8中的这些管道符。

好消息是,文件中列7和列9(|2016)的数据相同,因此我可以使用sed进行操作,如下:

sed 's/|/ /7g;s/.\(|2016\)/|\1/' 

然而,这将会改变第七个“|”符号之后的所有其余“|”符号,直到该行结束。我的问题是如何使它更改第七个“|”符号之后的所有“|”符号为空格,但仅限于“|2016”列之前?谢谢。

"en" 是第七列,因此 |en|col|u|m|n8|2016 是第7、8和9列,其中第8列有额外的竖线。 - mk97
еҪ“ж–Ү件еҸӘжңүдёҖиЎҢж—¶пјҢжӮЁеҸҜд»Ҙжү§иЎҢд»ҘдёӢж“ҚдҪңпјҡ col8=$(sed 's/\([^|]*|\)\{7\}\(.*\)|2016.*/\2/' file ); echo "Debug line: col8=${col8}, fixed ${col8//|/ }"; sed 's/^\(\([^|]*|\)\{7\}\).*|2016/\1'"${col8//|/ }"'|2016/' fileгҖӮиҝҷеҜ№дәҺжҷ®йҖҡж–Ү件зҡ„й—®йўҳ并дёҚжңүеё®еҠ©пјҢеӣ дёәжӮЁйңҖиҰҒиҝӣиЎҢдёҖдёӘйқһеёёзј“ж…ўзҡ„whileеҫӘзҺҜгҖӮ - Walter A
7个回答

1

在Lars提供的基础上,以下内容适用于所有版本的sed:

sed -e ':b' -e 's/\(|column7|\)\(.*\)|\(.*|2016|\)/\1\2 \3/' -e 'tb' inputfile

这是通过重复替换嵌入的分隔符,直到找不到替换模式为止来实现的。Sed的命令只有在上一个替换成功时才会转到标签。
我们使用更经典的BRE以确保与sed解释竖杠为“或”分隔符的ERE兼容。
Sed脚本被分成单独的选项,因为一些变体的sed要求标签引用位于“行末”,而参数的终止被认为等同于行末。(GNU sed不需要这样做,但其他一些seds需要。)
但正如anubhava在评论中指出的那样,这是一种较差的方法,因为如果输入数据包括第二个<2016|>,则会失败。
另一种解决方案(如果您正在运行bash)可能是将字段放入数组中,然后合并元素:
#!/usr/bin/env bash

input="column1|column2|column3|column4|column5|column6|column7|**col|u|lm|n8**|2016|column10|column11|column12|column13|column14|"

IFS=\| read -a a <<< "$input"

while [ "${a[8]}" != "2016" ]; do
  a[7]="${a[7]} ${a[8]}"   # merge elements
  unset a[8]               # delete merged element
  a=( "${a[@]}" )          # renumber array
done

printf "%s|" "${a[@]}"

请注意,默认情况下,Bash 数组从索引 0 开始。readarray 内置函数允许您为索引指定替代起点(-O),但该内置函数始于 Bash 版本 4,而在很多版本 3 环境中仍然存在。因此,为了移植性,请使用 read -a
还要注意,如果没有进一步的错误检查,上面的脚本会进入无限循环,如果由于某种原因在输入数据中没有“2016”字段。 :-)

如果在“col|um|n8”的“|2016”右边出现另一个“|2016”,那么这将会出错。 - anubhava
这个解决方案对我不起作用,用列“en”的实际值替换column7 sed -e ':b' -e 's/\(|en|\)\(.*\)|\(.*|2016|\)/\1\2 \3/' -e 'tb' inputfile在列8的值中仍然存在管道符。 - mk97
@mk97,如果我们能使用真实数据进行测试,也许调试你遇到的问题会更容易。我提供的sed脚本在FreeBSD的sed中使用你问题中提供的示例数据时可以正常工作。 - ghoti
1
@anubhava,是的,那是真的。对我来说很幸运,问题中提供的示例数据并不包括其中任何一个。尽管如此,我还是添加了我喜欢的一种bash解决方案。 - ghoti
@ghoti 我使用通用名称,因为真实数据是专有的,我不舒服发布它。但第7列和第9列是通用的,所以第7列、第8列和第9列的格式为 |en|column8|2016,其中column8可能是类似于 value|with|pipes|instead|of|spaces 的东西。 - mk97

1

使用您的示例输入,我在GNU sed 4.2.2上可以正常工作:

sed -r ':start s/(column7.)([^\|]*?)\|(.*?.2016)/\1\2 \3/; t start' file

它逐个替换column7..2016之间的管道符。在成功替换后,t会返回到:start标签以进行另一个替换尝试。


1
这在非GNU sed中不起作用。(我不知道它在GNU sed中是否有效。) - ghoti
那个对我有用,但是也替换了“2016”列中的管道为一个空格,需要保留那些管道... 所以输出结果是 |column7|col u m n8 2016。 - mk97
你在 (.*?.2016) 中的 2016 前面加了 . 吗? - Lars Fischer
嗯,我不太确定我做了什么,但现在它正常运行了。我可能是复制/粘贴出了问题。 - mk97

1

这里是一个使用perl编写的解决方案,即使在行中再次出现|2016的情况下也可以正常工作:

cat file
column1|column2|column3|column4|column5|column6|en|col|u|lm|n8|2016|column10|column11|2016|

perl -pe 's/(en\|[^|]*|(?<!^)\G[^|]*)\|(?!2016)/$1 /g' file

column1|column2|column3|column4|column5|column6|en|col u lm n8|2016|column10|column11|2016|

这个正则表达式使用PCRE构造\G,它断言该位置在前一个匹配的结尾或第一个匹配的字符串的开头。

正则表达式演示


1
我点赞了这个 Perl 解决方案,因为它也起作用了,但是上面的正确答案是针对 sed 的... 无论如何,谢谢和回复的每个人! - mk97

0

这个问题真的很有趣,我点赞了它,但无法用 sedawk 解决它。

我在 Python 中尝试并解决了它。我不提供“正式答案”,但提供一些想法:)

$cat sample.csv
column1|column2|column3|column4|column5|column6|column7|col|u|lm|n8|2016|column10|column11|column12|column13|column14|

我的代码:

$cat test.py                                                                                                                                                                           
import re
REGEX = ur"column7\|(.+?)\|2016+?"

with open("sample.csv", "r") as inputs:
    for line in inputs:
        matches = re.findall(REGEX, line)
        column8 = matches[0]
        new_column8 = column8.replace("|", "")
        print line.replace(column8, new_column8)

结果:

$python test.py                                                                                                                                                                       
column1|column2|column3|column4|column5|column6|column7|colulmn8|2016|column10|column11|column12|column13|column14|

0

使用GNU awk的第三个参数来匹配():

$ awk 'match($0,/(([^|]*[|]){7})(.*)(\|2016\|.*)/,a){gsub(/\|/," ",a[3]); $0=a[1] a[3] a[4]} 1' file
column1|column2|column3|column4|column5|column6|column7|**col u lm n8**|2016|column10|column11|column12|column13|column14|

0

当文件只有一行时,您可以执行以下操作: col8 = $(sed 's /([^ |] |){7}(。 )| 2016. / \ 2 /' file) echo“调试行:col8 = $ {col8},fixed $ {col8 // |}” sed 's / ^(([^ |] |){7})。* | 2016 / \ 1' "$ {col8 // |} "| 2016 /'文件

当您知道唯一的字符或字符串时,可以对具有多行的文件执行相同的操作。我将使用mk97 作为唯一字符串:


0
这可能适用于您(GNU sed):
sed 's/|/&\n/7;:a;ta;s/\n\(|2016|\)/\1/;s/\n|/ \n/;ta;s/\n\(.\)/\1\n/;ta' file

在字段八的开头添加一个换行符。如果换行符出现在字段九之前,请将其删除。如果换行符后面跟着一个|,请用一个空格替换|并将换行符移动到一个字符上。如果换行符后面没有跟着一个|,请将换行符移动到一个字符上。
注意:在任何成功的替换循环中,都要将占位符:a放在那里。

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