字符替换仅限于每个输入行的一部分

Substitution of characters limited to part of each input line

有一个文件,例如。 Inventory.conf 行如下:

Int/domain—home.dir=/etc/int

我需要在 = 之前而不是之后替换 /。 结果应该是:

Int_domain_home_dir=/etc/int

我尝试了几个 sed 命令,但 none 似乎符合我的需要。

使用 GNU sed:

echo 'Int/domain—home.dir=/etc/int' | sed 'h;s/[^=]*//;x;s/=.*//;s/[/—.]/_/g;G;s/\n//'

输出:

Int_domain_home_dir=/etc/int

参见:man sed。我假设你也想替换点。

如果perl解决方案没问题:

$ echo 'Int/domain-home.dir=/etc/int' | perl -pe 's#^[^=]+#$&=~s|[/.-]|_|gr#e'
Int_domain_home_dir=/etc/int
  • ^[^=]+ 字符串匹配从行首到但不包括第一次出现的 =
  • $&=~s|[/.-]|_|gr 对匹配的字符串执行另一个替换
    • 将所有 /.- 字符替换为 _
    • r修饰符会return修改后的字符串
  • e修饰符允许在替换部分使用表达式而不是字符串
  • # 用作定界符以避免必须在字符 class [/.-]
  • 内转义 /

此外,正如@mklement0 所建议的,我们可以使用翻译而不是内部替换

$ echo 'Int/domain-home.dir=/etc/int' | perl -pe 's#^[^=]+#$&=~tr|/.-|_|r#e'
Int_domain_home_dir=/etc/int


请注意,我已经更改了示例输入,使用 - 而不是 ,这是 OP 根据评论

似乎想要的

您要求的是 sed 解决方案,但 awk 解决方案在这种情况下更简单并且性能更好,因为您可以轻松拆分该行按 = 分为 2 个字段,然后有选择地将 gsub() 应用于第一个字段以替换感兴趣的字符:

$ awk -F= '{ gsub("[./-]", "_", ); print  FS  }' <<< 'Int/domain-home.dir=/etc/int'
Int_domain_home_dir=/etc/int
  • -F= 告诉 awk= 将输入拆分为字段) 包含行的前半部分,在 = 之前,</code>(第二个字段)第二半,在 <code>= 之后;使用 -F 选项设置变量 FS,输入字段分隔符。

  • gsub("[./-]", "_", ) 全局替换集合 [./-] 中的所有字符为 </code> 中的 <code>_ - 即所有出现的 . , / 或第一个字段中的 - 分别替换为 _

  • print FS 打印结果:修改后的第一个字段 (</code>),然后是 <code>FS(即 =),然后是(未修改的)第二个字段 (</code>).</p></li> </ul> <p>请注意,我使用的是 ASCII 字符。 <code>-HYPHEN-MINUS,代码点 0x2d)在 awk 脚本中,即使您的示例输入包含 Unicode 字符。 (EM DASH, U+2014, UTF-8 编码 0xe2 0x80 0x94).
    如果你真的想匹配 that,只需在上面的命令中替换它,但请注意 macOS 上的 awk 版本无法正确处理。

    另一种选择是将 iconv 与 ASCII 音译结合使用,将长破折号翻译成常规 ASCII -:

    iconv -f utf-8 -t ascii//translit <<< 'Int/domain—home.dir=/etc/int' |
      awk -F= '{ gsub("[./-]", "_", ); print  FS  }' 
    

    perl 也提供了一个优雅的解决方案:

    $ perl -F= -ane '$F[0] =~ tr|-/.|_|; print join("=", @F)' <<<'Int/domain-home.dir=/etc/int'
    Int_domain_home_dir=/etc/int
    
    • -F=,就像 Awk 一样,告诉 Perl 在将行拆分为字段时使用 = 作为分隔符

    • -ane 激活字段拆分 (a),关闭隐式输出 (n),并且 e 告诉 Perl next 参数是要执行的表达式(命令字符串)。

    • 每行分割成的字段存储在数组@F中,其中$F[0]指的是第一个字段。

    • $F[0] =~ tr|-/.|-| 将所有出现的 -/. 翻译(替换)为 _.

    • print join("=", @F) 从字段重建输入行 - 现在修改了第一个字段 - 并打印结果。

    根据所使用的 Awk 实现,这实际上可能更快(见下文)。


    sed 不是这项工作的最佳工具也反映在解决方案的相对性能 中:

    来自我的 macOS 10.12 机器(GNU sed 4.2.2,Mawk awk 1.3.4,perl v5.18.2,使用输入文件 file ,其中包含样本输入行的 100 万个副本)- 对它们持保留态度,但数字的 比率 很有趣;首先是最快的解决方案:

    # This answer's awk answer.
    # Note: Mawk is much faster here than GNU Awk and BSD Awk.
    $ time awk -F= '{ gsub("[./-]", "_", ); print  FS  }' file >/dev/null
    real    0m0.657s
    
    # This answer's perl solution:
    # Note: On macOS, this outperforms the Awk solution when using either
    #       GNU Awk or BSD Awk.
    $ time perl -F= -ane '$F[0] =~ tr|-/.|_|; print join("=", @F)' file >/dev/null
    real    0m1.656s
    
    # Sundeep's perl solution with tr///
    $ time perl -pe 's#^[^=]+#$&=~tr|/.-|_|r#e' file >/dev/null
    real    0m2.370s
    
    # Sundeep's perl solution with s///
    $ time perl -pe 's#^[^=]+#$&=~s|[/.-]|_|gr#e' file >/dev/null
    real    0m3.540s
    
    # Cyrus' solution.
    $ time sed 'h;s/[^=]*//;x;s/=.*//;s/[/.-]/_/g;G;s/\n//' file >/dev/null
    real    0m4.090s
    
    # Kenavoz' solution.
    # Note: The 3-byte UTF-8 em dash is NOT included in the char. set,
    #       for consistency of comparison with the other solutions.
    #       Interestingly, adding the em dash adds another 2 seconds or so.
    $ time sed ':a;s/[-/.]\(.*=\)/_/;ta' file >/dev/null
    real    0m9.036s
    

    如您所见,awk 解决方案是迄今为止最快的,线路内部循环 sed 解决方案的性能预计最差,约为 12 倍。

带有 t 循环的 Sed (BRE):

$ sed ':a;s/[-/—.]\(.*=\)/_/;ta;' <<< "Int/domain—home.dir=/etc/int"
Int_domain_home_dir=/etc/int

当找到 -/—. 字符之一时,它将替换为 _。以下文本最多 = 被捕获并使用反向引用输出。如果先前的替换成功,t 命令循环到标签 :a 以检查进一步的替换。

编辑:

如果您低于 BSD/Mac OSX(感谢@mklement0):

sed -e ':a' -e 's/[-/—.]\(.*=\)/_/;ta'