我有一个管道分隔文件,其中一列中的某些值/记录在值本身中包含管道,使其看起来像是有比实际更多的列-请注意"第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/'
但是,这将更改从第7个管道到管线末端的所有管道。我的问题是,如何将所有管道更改为第7个管道之后的空间,直到"|2016"列?
感谢
对于您的示例输入,这对我来说适用于GNU sed 4.2.2.:
sed -r ':start s/(column7.)([^|]*?)|(.*?.2016)/12 3/; t start' file
它替换column7.
和.2016
之间的管道,每次一个管道。成功替换后,t
gotos返回:start
标签进行另一次替换尝试。
以下是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
,它断言上一个匹配的末尾或第一个匹配的字符串的开头的位置。
RegEx演示
在Lars提供的基础上,以下内容应适用于所有版本的sed:
sed -e ':b' -e 's/(|column7|)(.*)|(.*|2016|)/12 3/' -e 'tb' inputfile
这是通过重复替换嵌入的分隔符来实现的,直到找不到替换模式为止。Sed的t
命令仅在上一次替换成功的情况下分支到:b
标签。
我们使用更经典的BRE,既为了兼容性,也为了避免sed将垂直条解释为ERE中的"或"分隔符。
sed脚本被分成单独的-e
选项,因为一些sed变体要求标签引用"在行的末尾",并且-e
的参数的终止被认为等同于行的末尾。(GNU sed不需要这个,但其他一些sed需要。)
但正如anubhava在评论中指出的那样,这是一种较差的方法,因为如果输入数据在第9列右侧的某个位置包括第二个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"字段,那么在没有进一步错误检查的情况下,上述脚本将进入一个无休止的循环。:-)
这个问题我真的很感兴趣,我投了赞成票,但在sed
或awk
中没有解决
我在python中尝试过并成功了。我不提供official answer
,但提供了一些想法:)
$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|
使用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|
当文件只有一行时,您可以col8=$(sed的/([^|]|){7}(.)|2016./\2/'文件)echo"调试行:col8=${col8},已修复${col8//|/}"sed的/^(([^|]|){7}).*|2016/\1'"${col8//|/}"'|2016/'文件
当您知道一个唯一的字符或字符串时,您可以对具有更多行的文件执行同样的操作。我将使用mk97
作为唯一字符串:
这可能对你有用(GNU sed):
sed 's/|/&n/7;:a;ta;s/n(|2016|)/1/;s/n|/ n/;ta;s/n(.)/1n/;ta' file
在字段八的开头添加一行换行符。如果换行符出现在第九字段之前,则删除它。如果换行符后面跟着|
,则用空格替换|
,并在字符上打乱换行符。如果换行符后面没有|
,则在字符上打乱换行符。
注意:在到位置保持器:a
的任何成功的替换循环上。