字符替换仅限于每个输入行的一部分
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'
有一个文件,例如。 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,Mawkawk
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'