2011-11-25 58 views
9

我有这个测试文件。使用sed替换文本只是在引号中

[[email protected] ~]# cat f.txt 
"a aa" MM "bbb b" 
MM MM 
MM"b b " 
[[email protected] ~]#

我想用引号替换引号中的所有空格字符,只是在引号中。所有不包括引号的字符都不应该被触及。也就是说,我要的是类似于:

"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_"

可以这样使用的sed实施?

谢谢,

+2

顺便说一句:好问题 - 特别是有很好的示例输入和所需的输出。 –

回答

8

这是一个完全不重要的问题。

这适用于下划线代替,引号里的第一空间:

$ sed 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt 
"a_aa" MM "bbb_ b" 
MM MM 
MM"b_b " 
$ 

在这个例子中,在里面有任何的报价不超过两个空间,人们很容易简单地重复命令,但它给出了一个不正确的结果:

$ sed -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' \ 
>  -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt 
"a_aa"_ MM "bbb_ b" 
MM MM 
MM"b_b_" 
$ 

如果你的的sed版本支持“扩展正则表达式”,那么这个工程的样本数据:

$ sed -E \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> f.txt 
"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_" 
$ 

对于双引号内的每个空格,你必须重复那个可怕的正则表达式 - 因此对于第一行数据来说是三次。

正则表达式可以如解释:

  • 开始在一行的开头,
  • 查找的“零个或多个非报价,随后任选地报价,空格或引号序列和一个报价',整个组件重复零次或多次,
  • 后跟一个报价,零个或多个非引号,非空格,一个空格,零个或多个非引号和一个报价。
  • 用前导部分替换匹配材料,当前引用段落开始时的材料,下划线和当前引用段落的尾部材料。

因为起步锚的,这必须每空重复一次......但sed具有循环结构,所以我们可以做到这一点:

$ sed -E -e ':redo 
>   s/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/ 
>   t redo' f.txt 
"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_" 
$ 

:redo定义了一个标签; s///命令与以前一样;如果自从上一次读取一行或跳转到标签以来进行了任何替换,则t redo命令将跳转到标签。


鉴于该意见的讨论中,有几个值得一提的几点:

  1. -E选项适用于sed在MacOS X(10.7.2测试)。GNU版本sed的相应选项是-r(或--regex-extended)。 -E选项与grep -E(它也使用扩展正则表达式)一致。 “经典Unix系统”不支持sed(Solaris 10,AIX 6,HP-UX 11)的ERE。

  2. 可以代替我用了?(这是强制使用的ERE,而不是BRE的唯一字符)与*,然后用括号(需要反斜杠在一个BRE他们面前处理使他们成为捕获括号),使脚本:

    sed -e ':redo 
         s/^\(\([^"]*\("[^ "]*"\)*\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g 
         t redo' f.txt 
    

    这将产生相同的输入相同的输出 - 我试着输入一些稍微复杂的图案:

    "a aa" MM "bbb b" 
    MM MM 
    MM"b b " 
    "c c""d d""e e" X " f "" g " 
    "C C" "D D" "E E" x " F " " G " 
    

    氏s给出的输出:

    "a_aa" MM "bbb__b" 
    MM MM 
    MM"b_b_" 
    "c_c""d_d""e__e" X "_f_""_g_" 
    "C_C" "D_D" "E__E" x "_F_" "_G_" 
    
  3. 即使BRE符号,sed支持\{0,1\}表示法指定0或1次出现先前RE术语,所以?版本可以使用被转换为BRE:

    sed -e ':redo 
         s/^\(\([^"]*\("[^ "]*"\)\{0,1\}\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g 
         t redo' f.txt 
    

    这产生与其他选择相同的输出。

+0

谢谢你。优秀的解决方但是扩展的正则表达式开关在我的系统上是*** - r ***。 –

+0

@JonathanLeffler优秀的正则表达式使用,特别是'(“[^”] *“)?'碰撞替代,但为什么'?'而不是'*'? – potong

+0

我认为你可以使用'?'或'* ''成功了('*'处理样本数据)。我使用'?'是因为它可能有助于限制正则表达式的回溯数量,这非常复杂(这不是我想要的正则表达式必须急于破译!)。 –

0

一个莫名其妙不寻常的答案XSLT 2.0:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0"> 
    <xsl:output method="text"></xsl:output> 
    <xsl:template name="init"> 
     <xsl:for-each select="tokenize(unparsed-text('f.txt'),'&#10;')"> 
      <xsl:for-each select="tokenize(.,'&quot;')"> 
       <xsl:value-of select="if (position() mod 2 = 0) 
        then concat('&quot;',translate(.,' ','_'),'&quot;') else ."></xsl:value-of> 
      </xsl:for-each> 
      <xsl:text>&#10;</xsl:text> 
     </xsl:for-each> 
    </xsl:template>  
</xsl:stylesheet> 

为了测试是否,只得到sourceforge上saxon.jar并使用以下命令行:

java -jar saxon9.jar -it:init regexp.xsl 

XSLT文件包含对f.txt的引用,则文本文件必须与xslt文件位于同一目录中。通过给样式表一个参数可以很容易地改变它。

它在一次通过。

0

如果引用的文本全部在不同的行上,这将非常简单。所以一种方法是分割文本,这样你就可以做到,做简单的转换,然后重建线条。

拆分文本是容易的,但我们需要的是为

  1. 已经被我们添加的文件
  2. 在目前的换行来区分

为了做到这一点,我们可以用符号表示它属于哪个类的每一行结束。我会用1和2,直接对应上面的。在SED,我们有:

sed -e 's/$/1/' -e 's/"[^"]*"/2\n&2\n/g' 

这将产生:

2 
"a aa"2 
    MM 2 
"bbb b"2 
1 
MM MM1 
MM2 
"b b "2 
1 

这很容易进行改造,只需使用

sed -e '/".*"/ s/ /_/g' 

2 
"a_aa"2 
    MM 2 
"bbb__b"2 
1 
MM MM1 
MM2 
"b_b_"2 
1 

最后,我们需要把它放回去。这实际上是在SED很可怕,但使用的保留空间是可行的:(这将是更清晰了很多,例如,AWK)

sed -e '/1$/ {s/1$//;H;s/.*//;x;s/\n//g}' -e '/2$/ {s/2$//;H;d}' 

管的三个步骤在一起,你就大功告成了。

0

这可能会为你工作:

sed 's/^/\n/;:a;s/\(\n[^"]*"[^ "]*\) \([^"]*"\)\n*/\1_\2\n/;ta;s/\n//;ta;s/\n//' file 

说明:

前面加上一个\n到线的起点,这将被用来沿着换人磕碰。在"之内替换一个_,然后在那里为\n准备好下一轮替换。取代所有后,删除\n并重复。当发生所有替换时,请删除\n分隔符。

或该:

sed -r ':a;s/"/\n/;s/"/\n/;:b;s/(\n[^\n ]*) ([^\n]*\n)/\1_\2/g;tb;s/\n/%%%/g;ta;s/%%%/"/g' file 

说明:

“与\n小号的替换第一组""。用_替换换行符之间的第一个空格,重复。将\n替换为一个唯一的分隔符(%%%),从头开始重复。最后用"代替所有%%%

的第三种方式:

sed 's/"[^"]*"/\n&\n/g;$!s/$/@@@/' file | 
sed '/"/y/ /_/;1{h;d};H;${x;s/\n//g;s/@@@/\n/g;p};d' 

说明:

环绕所有引用的表达式("...")与换行符(\n的)。在除最后一行之外的所有行上插入行尾分隔符@@@。将结果传递给第二个sed命令。将的全部内容翻译为_,其中的内容为"。将每条线存放在容纳空间(HS)中。在文件中,交换到HS的结束,并删除所有\n的,并与\n代替结束行分隔符的

最后:

sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /' file | sh 

或GNU sed的:

sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /e' file 

留给读者解决。