有一个文件,例如。 Inventory.conf的行如:
Int/domain—home.dir=/etc/int
我需要在/
之前替换—
和=
,而不是之后。
结果应该是:
Int_domain_home_dir=/etc/int
我尝试了几个sed
命令,但似乎没有一个符合我的需要。
答案 0 :(得分:2)
您要求的是sed
解决方案,但 awk
解决方案更简单,在这种情况下表现更好,因为您可以轻松地将该行拆分为2个字段按=
,然后有选择地将gsub()
仅应用于第一个字段,以替换感兴趣的字符:
$ awk -F= '{ gsub("[./-]", "_", $1); print $1 FS $2 }' <<< 'Int/domain-home.dir=/etc/int'
Int_domain_home_dir=/etc/int
-F=
告诉awk
将输入拆分为=
字段,其中输入信号会导致$1
(第1个字段)包含第一个字段在=
之后,在$2
之前的一半,以及在=
之后的-F
(第二场)。使用FS
选项设置变量gsub("[./-]", "_", $1)
,即输入字段分隔符。
[./-]
全局将_
中的所有字符替换为$1
中的.
- 即所有出现的/
,{{1}第1个字段中的{}或-
每个都替换为_
。
print $1 FS $2
打印结果:修改后的第1个字段($1
),后跟FS
(=
),然后是(未修改)第二个字段($2
)。
请注意,我使用过ASCII char。即使您的示例输入包含Unicode字符,-
脚本中的0x2d
( HYPHEN-MINUS ,代码点awk
)也是如此。 —
( EM DASH ,U+2014
,UTF-8编码0xe2 0x80 0x94
)。
如果你真的想匹配那个,只需在上面的命令中替换它,但请注意macOS上的awk
版本无法正确处理。
另一种选择是使用带有ASCII音译的iconv
,将em破折号转换为常规ASCII -
:
iconv -f utf-8 -t ascii//translit <<< 'Int/domain—home.dir=/etc/int' |
awk -F= '{ gsub("[./-]", "_", $1); print $1 FS $2 }'
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 argument是要执行的表达式(命令字符串)。
每行分割成的字段存储在数组@F
中,其中$F[0]
指的是第一个字段。
$F[0] =~ tr|-/.|-|
将-
,/
和.
的所有内容翻译(替换)为_
。
print join("=", @F)
从字段重建输入行 - 现在修改了第1个字段 - 并打印结果。
根据所使用的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("[./-]", "_", $1); print $1 FS $2 }' 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/[-/.]\(.*=\)/_\1/;ta' file >/dev/null
real 0m9.036s
正如您所看到的,到目前为止,awk
解决方案速度最快,行内部循环sed
解决方案可预测性能最差,约为12倍。
答案 1 :(得分:2)
带有t
循环(BRE)的Sed:
$ sed ':a;s/[-/—.]\(.*=\)/_\1/;ta;' <<< "Int/domain—home.dir=/etc/int"
Int_domain_home_dir=/etc/int
如果找到其中一个-/—.
字符,则将其替换为_
。接下来的文本将被捕获并使用反向引用输出=
。如果先前的替换成功,t
命令将循环标记:a
以检查是否有进一步的替换。
修改强>
如果您在BSD / Mac OSX下(感谢@ mklement0):
sed -e ':a' -e 's/[-/—.]\(.*=\)/_\1/;ta'
答案 2 :(得分:1)
使用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
。我想你也想替换点。
答案 3 :(得分:1)
如果$ echo 'Int/domain-home.dir=/etc/int' | perl -pe 's#^[^=]+#$&=~s|[/.-]|_|gr#e'
Int_domain_home_dir=/etc/int
解决方案没问题:
^[^=]+
=
字符串匹配从排队开始到但不包括第一次出现的$&=~s|[/.-]|_|gr
/
对匹配的字符串执行另一次替换
.
或-
或_
字符替换为r
e
修饰符将返回修改后的字符串#
修饰符允许在替换部分/
用作分隔符,以避免在字符类[/.-]
$ echo 'Int/domain-home.dir=/etc/int' | perl -pe 's#^[^=]+#$&=~tr|/.-|_|r#e'
Int_domain_home_dir=/etc/int
另外,正如@ mklement0所建议的那样,我们可以使用translate代替内部替换
-
请注意,我更改了示例输入,—
代替DataFrame
,这是OP根据评论想要的内容