使用sed替换引号中的文本

时间:2011-11-25 07:18:46

标签: regex sed

我有这个测试文件。

[root@localhost ~]# cat f.txt 
"a aa"  MM  "bbb  b"
MM    MM
MM"b b "
[root@localhost ~]#

我想替换引号中的所有空格字符,注意,仅在引号中。不应触及引号中的所有字符。也就是说,我想要的是类似的东西:

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

可以使用 sed

来实现

谢谢,

4 个答案:

答案 0 :(得分: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选项适用于MacOS X上的sed(已测试10.7.2)。 GNU版sed的相应选项是-r(或--regex-extended)。 -E选项与grep -E(也使用扩展正则表达式)一致。 “经典Unix系统”不支持sed的ERE(Solaris 10,AIX 6,HP-UX 11)。

  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 "
    

    这给出了输出:

    "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\}表示法指定前一个RE术语的0或1次出现,因此?版本可以转换为BRE使用:

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

    这产生与其他替代品相同的输出。

答案 1 :(得分: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文件位于同一目录中。通过为样式表提供参数可以很容易地改变它。

一次通过。

答案 2 :(得分:0)

如果引用的文字全部在不同的行上,这将非常容易。因此,一种方法是拆分文本,这样你就可以进行简单的转换,然后重建线条。

分割文本很简单,但我们需要区分

的换行符
  1. 已存在于文件
  2. 由我们添加
  3. 为此,我们可以用一个表示它所属的类的符号结束每一行。我只使用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中实际上非常可怕,但使用保持空间是可行的:

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

    (这会更加清晰,例如,awk。)

    将这三个步骤连接起来,你就完成了。

答案 3 :(得分: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。将结果传递给第二个命令。将所有_的{​​{1}}翻译为",其中包含\n的行。将每一行存储在保留空间(HS)中。在文件末尾,切换到HS并删除所有\n并用sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /' file | sh 替换行尾分隔符

最后:

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

或GNU sed:

{{1}}

留给读者解决。