为什么sed不能取代重叠的模式

我有一个数据库卸载文件与<TAB>字符分隔的字段。 我通过sed运行这个文件,用<TAB> \ N <TAB>replace<TAB> <TAB>的任何出现。 这是这样的,当文件被加载到MySQL的\ N在解释为NULL。

sed命令的/ \ t \ t / \ t \ N \ t / g;' 除了它只replace第一个例子,例如“… <TAB> <TAB> <TAB> …”变成“… TAB N N TAB TAB …”。

如果我用't \ t \ t \ n \ t \ g; s \ \ t \ t \ t \ n \ t \ g;' 它取代了更多的实例。

我有一个概念,尽pipe/ g修饰符这是一个匹配的结束是另一个的开始。

任何人都可以解释发生了什么事情,并build议一个将工作或我需要循环的sed命令。

我知道我可以切换到awk,perl,python,但是我想知道sed中发生了什么。

我知道你想要sed,但是sed根本不喜欢这个,看起来它特意(见这里 )不会做你想要的。 但是,Perl会做到这一点(AFAIK):

 perl -pe 'while (s#\t\t#\t\n\t#) {}' <filename> 

解决方法是将每个选项卡替换为选项卡+ \ N; 然后删除所有不是紧跟在标签后面的\ N。

 sed -e 's/\t/\t\\N/g' -e 's/\\N\([^\t]\)/\1/g' 

…提供您的sed在分组括号之前使用反斜线(有不想反斜杠的sed方言;如果这不适合您,请尝试没有它们)。

正确,即使使用/g ,sed也不会匹配再次替换的文本。 因此,读取<TAB><TAB>并输出<TAB>\N<TAB> ,然后从输入流中读取下一个内容。 请参阅http://www.grymoire.com/Unix/Sed.html#uh-7

在一个支持lookahead的正则表达式语言中,你可以用前瞻来解决这个问题。

那么, sed只是按照设计工作。 输入行被扫描一次,而不是多次。 如果sed默认使用重新扫描输入行来处理重叠模式,那么可能有助于看看后果:在这种情况下,即使是简单的替换也会有很大不同 – 有些人可能会反直觉地说 – 例如

  • s/^/ /在行的开头插入一个空格将永远不会终止
  • s/$/foo/将foo附加到每行 – 同样
  • s/[AZ][AZ]*/CENSORED/s/[AZ][AZ]*/CENSORED/替换大写字母 – 同样

可能还有很多其他情况。 当然,这些都可以用一个替代修饰符来补救,但是在设计sed时,选择了当前的行为。

没有不同的Perl解决方案,这对我使用纯粹的sed

 sed ':repeat; /\t\t/{ s|\t\t|\t\n\t|g; b repeat }' 

说明

  • :repeat是一个标签,用于分支命令,类似于批处理
  • /\t\t/表示匹配模式2选项卡。 如果匹配的模式,第二个/之后的命令被执行。
  • {} – 在这种情况下,match命令之后的命令是一个组。 因此,如果匹配模式被满足,则组中的所有命令都被执行。
  • s|\t\t|\t\n\t|g; – 标准用tab-newline-tab替换2个选项卡。 我仍然使用全局的,因为如果你有15个选项卡,你只需要循环两次,而不是14次。
  • b repeat意味着总是转到(分支)标签repeat

所以它是这样的。 只要有两个选项卡的模式匹配,请继续重复( repeat )。

虽然可以说你可以做两个完全相同的全局替换并称之为好,但是同样的技术可以在更复杂的情况下工作。

正如@ thorn-blake指出的那样,sed只是不支持像lookahead这样的高级功能,所以你需要像这样做一个循环。

简洁版本

这可以缩短到

 sed ':r;/\t\t/{s|\t\t|\t\n\t|g; br}' 

苹果系统

和Mac(但仍然Linux / Windows兼容)版本:

 sed $':r\n/\t\t/{ s|\t\t|\t\\\n\t|g; br\n}' 
  • 在BSD sed中,制表符必须是字面的
  • 换行符需要同时是文字和转义的,因此单斜杠(在$被它处理之前是\,使它成为单个文字斜线)加上\ n,成为一个实际的换行符
  • 标签名称(:r)和分支命令(br)都必须以换行符结尾。 分号和空格被BSD中的标签名称/分支命令所占用,这使得它们都非常混乱。