hug*_*omg 19 sed text-processing
我想编写一个 shell 脚本,用 BB 替换 A,用 AA 替换 B。例如,AYB
变成BBYAA
。对于这种事情,我通常的解决方案是将多个调用链接到 sed,如下所示:
sed 's/A/BB/g;s/B/AA/g'
Run Code Online (Sandbox Code Playgroud)
但是,这在这种情况下不起作用,因为A
最终被翻译成AAAA
而不是BB
. tr
似乎也不是一种选择,因为替换文本长于一个字符。还有什么我可以做的吗?如果它使用 sed 或 tr 以外的其他东西也没关系。
Kus*_*nda 14
您无法使用 in 中的单个替换来完成整个操作sed
,但是您可以根据两个子字符串A
和B
是单个字符还是更长的字符串,以不同的方式正确地完成整个操作。
假设两个子A
和B
是单字符...
你想转变AYB
成BBYAA
. 去做这个,
A
,以B
及B
到A
使用y/AB/BA/
。A
用AA
using替换新字符串中的每一个s/A/AA/g
。B
用BB
using替换新字符串中的每一个s/B/BB/g
。$ echo AYB | sed 'y/AB/BA/; s/B/BB/g; s/A/AA/g'
BBYAA
Run Code Online (Sandbox Code Playgroud)
结合最后两个步骤得到
$ echo AYB | sed 'y/AB/BA/; s/[AB]/&&/g'
BBYAA
Run Code Online (Sandbox Code Playgroud)
事实上,这里的操作顺序并不重要:
$ echo AYB | sed 's/[AB]/&&/g; y/AB/BA/'
BBYAA
Run Code Online (Sandbox Code Playgroud)
的sed
编辑命令y///
翻译中的字符在它的第一个参数在其第二个参数对应的字符,有点像tr
实用程序执行。这是在一个操作中完成的,所以您不需要使用临时为交换A
和B
中y/AB/BA/
。在一般情况下,y///
是多比什么如转换单个字符更快s///g
的(因为没有正则表达式的时候),并且它也能插入新行用绳子\n
,它的标准s///
命令不能做(s///
在GNUsed
能明显这样做,因为非便携式便利扩展)。
该&
在的替换部分的字符s///
的命令将通过在任何相匹配的第一个参数的表达式来代替,所以s/[AB]/&&/g
将增加一倍的任何A
或B
输入数据中的字符。
对于多字符子,假设子串是不同的(即,一个子未在其它发现,如在的情况下oo
和foo
),使用像
$ echo fooxbar | sed 's/foo/@/g; s/bar/foofoo/g; s/@/barbar/g'
barbarxfoofoo
Run Code Online (Sandbox Code Playgroud)
即,通过在数据中找不到的中间字符串交换两个字符串。请注意,中间字符串可以是数据中未找到的任何字符串,而不仅仅是单个字符。
gle*_*man 13
这是需要循环的问题,以便您可以同时搜索两种模式。
awk '
BEGIN {
regex = "A|B"
map["A"] = "BB"
map["B"] = "AA"
}
{
str = $0
result = ""
while (match(str, regex)) {
found = substr(str, RSTART, RLENGTH)
result = result substr(str, 1, RSTART-1) map[found]
str = substr(str, RSTART+RLENGTH)
}
print result str
}
'
Run Code Online (Sandbox Code Playgroud)
当然,如果 Perl 可用,则有一个等效的 oneliner:
perl -pe '
BEGIN { %map = ("A" => "BB", "B" => "AA"); }
s/(A|B)/$map{$1}/g;
'
Run Code Online (Sandbox Code Playgroud)
如果没有任何模式包含特殊字符,您还可以动态构建正则表达式:
perl -pe '
BEGIN {
%map = ("A" => "BB", "B" => "AA");
$regex = join "|", keys %map;
}
s/($regex)/$map{$1}/g;
'
Run Code Online (Sandbox Code Playgroud)
顺便说一句,Tcl 有一个内置命令,用于调用string map
,但编写 Tcl oneliners 并不容易。
演示按长度对键进行排序的效果:
没有排序
$ echo ABBA | perl -pe '
BEGIN {
%map = (A => "X", BB => "Y", AB => "Z");
$regex = join "|", map {quotemeta} keys %map;
print $regex, "\n";
}
s/($regex)/$map{$1}/g
'
Run Code Online (Sandbox Code Playgroud)
A|AB|BB
XYX
Run Code Online (Sandbox Code Playgroud)
带排序
$ echo ABBA | perl -pe '
BEGIN {
%map = (A => "X", BB => "Y", AB => "Z");
$regex = join "|", map {quotemeta $_->[1]}
reverse sort {$a->[0] <=> $b->[0]}
map {[length, $_]}
keys %map;
print $regex, "\n";
}
s/($regex)/$map{$1}/g
'
Run Code Online (Sandbox Code Playgroud)
BB|AB|A
ZBX
Run Code Online (Sandbox Code Playgroud)
在 perl 中对“普通”排序与 Schwartzian 进行基准测试:子程序中的代码直接从文档中提取sort
#!perl
use Benchmark qw/ timethese cmpthese /;
# make up some key=value data
my $key='a';
for $x (1..10000) {
push @unsorted, $key++ . "=" . int(rand(32767));
}
# plain sorting: first by value then by key
sub nonSchwartzian {
my @sorted =
sort { ($b =~ /=(\d+)/)[0] <=> ($a =~ /=(\d+)/)[0] || uc($a) cmp uc($b) }
@unsorted
}
# using the Schwartzian transform
sub schwartzian {
my @sorted =
map { $_->[0] }
sort { $b->[1] <=> $a->[1] || $a->[2] cmp $b->[2] }
map { [$_, /=(\d+)/, uc($_)] }
@unsorted
}
# ensure the subs sort the same way
die "different" unless join(",", nonSchwartzian()) eq join(",", schwartzian());
# benchmark
cmpthese(
timethese(-10, {
nonSchwartzian => 'nonSchwartzian()',
schwartzian => 'schwartzian()',
})
);
Run Code Online (Sandbox Code Playgroud)
运行它:
$ perl benchmark.pl
Benchmark: running nonSchwartzian, schwartzian for at least 10 CPU seconds...
nonSchwartzian: 11 wallclock secs (10.43 usr + 0.05 sys = 10.48 CPU) @ 9.73/s (n=102)
schwartzian: 11 wallclock secs (10.13 usr + 0.03 sys = 10.16 CPU) @ 49.11/s (n=499)
Rate nonSchwartzian schwartzian
nonSchwartzian 9.73/s -- -80%
schwartzian 49.1/s 405% --
Run Code Online (Sandbox Code Playgroud)
使用 Schwartzian 变换的代码要快 4 倍。
其中比较函数仅 length
针对每个元素:
Benchmark: running nonSchwartzian, schwartzian for at least 10 CPU seconds...
nonSchwartzian: 11 wallclock secs (10.06 usr + 0.03 sys = 10.09 CPU) @ 542.52/s (n=5474)
schwartzian: 10 wallclock secs (10.21 usr + 0.02 sys = 10.23 CPU) @ 191.50/s (n=1959)
Rate schwartzian nonSchwartzian
schwartzian 191/s -- -65%
nonSchwartzian 543/s 183% --
Run Code Online (Sandbox Code Playgroud)
Schwartzian 使用这种廉价的排序功能要慢得多。
我们现在可以摆脱辱骂性评论吗?
使用awk
,您可以使用pattern1作为字段分隔符FS
,将replacement1用作输出字段分隔符OFS
。然后,遍历每个字段并替换模式2的replacement2:
awk '{for (f=1;f<=NF;f++){gsub(p,r,$f)} $1=$1}1' FS=A OFS=BB p=B r=AA file
Run Code Online (Sandbox Code Playgroud)
重点$1=$1
是强制重建记录,否则它将失败0A
例如。
这是符合 POSIX 标准的并且不涉及中间字符串,因此它是万无一失的。
我想一个解决方案是先将 A 或 B 替换为字符串中不存在的另一个字符,然后替换该字符。这样就避免了 A 和 B 之间的切换。虽然需要一串sed
's :
$ echo AYB | sed -e 's/A/#/g' -e 's/B/AA/g' -e 's/#/BB/g'
BBYAA
Run Code Online (Sandbox Code Playgroud)
您可以GNU sed
通过将 all 更改As
为 record separator来做到这一点\n
,它肯定不会出现。
echo AYB |
sed -e '
y/A/\n/
s/[\nB]/&&/g
y/\nB/BA/
'
BBYAA
Run Code Online (Sandbox Code Playgroud)
您可以使用以下方法通过一系列直接替换为任意输入文本完成此操作sed
:
sed 's/Q/Qz/g; s/A/Qa/g; s/B/AA/g; s/Qa/BB/g; s/Qz/Q/g;'
Run Code Online (Sandbox Code Playgroud)
对于此类问题,有一个通用解决方案,适用于您唯一可以做的就是直接替换的情况,并且适用于任意输入文本。
诀窍是您首先在文本的中间副本中创建一个标记空间(即变量空间),该空间可用于表示任意值,以便您在以后的替换中使用的标记不能存在于文本的中间副本中。在您进行进一步替换时输入文本。例如:
s/Q/Qz/g
Run Code Online (Sandbox Code Playgroud)
这使得文本不能再包含任何Q
后跟除,之外的任何内容z
,并且每个Qz
实际上都代表 a Q
。这意味着您可以自由使用Q
后跟任何字符,而不是z
代表您想要的任何字符。
在这种特定情况下,您也不能使用QA
and QB
,因为您要替换单个A
和B
字符。
因此,要生成您想要的整体替换,您将执行以下替换序列:
s/Q/Qz/g # Open your Q* token-space.
# You can now use any Q* other than Qz, QA, and QB to represent another value.
# The restriction to not use QA and QB is only because this specific case requires
# substituting for the single A and B characters.
s/A/Qa/g # Temporarily represent all A as Qa.
s/B/AA/g # Change all B to AA, as desired in the question.
s/Qa/BB/g # Change all Qa placeholders with BB.
s/Qz/Q/g # Restore all Q, closing the token-space.
Run Code Online (Sandbox Code Playgroud)
所以,总的来说,这可以写成:
sed 's/Q/Qz/g; s/A/Qa/g; s/B/AA/g; s/Qa/BB/g; s/Qz/Q/g;'
Run Code Online (Sandbox Code Playgroud)
这会产生输出:
$ echo AYB | sed 's/Q/Qz/g; s/A/Qa/g; s/B/AA/g; s/Qa/BB/g; s/Qz/Q/g;'
BBYAA
Run Code Online (Sandbox Code Playgroud)
发生了什么:
代换 | 缓冲区中的文本 | 评论 |
---|---|---|
s/Q/Qz/g |
AYB |
打开您的Q* 令牌空间。这对这个示例输入没有任何作用,但确保这将适用于任意输入文本。 |
s/A/Qa/g |
QaYB |
暂时将所有表示A 为Qa 。 |
s/B/AA/g |
QaYAA |
所有更改B 到AA ,可以根据需要在问题。 |
s/Qa/BB/g |
BBYAA |
用 更改所有Qa 占位符BB 。 |
s/Qz/Q/g |
BBYAA |
恢复所有Q ,关闭令牌空间。同样,对这个示例输入文本没有任何作用,但需要处理任意输入文本。 |
对于标记空间的第一个字符,您可以使用任何单个字符。我一般用Q
,因为它在文本中出现频率低,是ASCII字符集中的一个字母,好记。Z
也适合。如果您使用更宽的字符集,则选择使用频率更低的字符(可能是符号)是有利的。出于性能原因,理想的情况是选择一个在您将使用的文本中不存在的字符,这样第一个和最后一个替换实际上不会做任何事情。然而,这只是性能和空间问题,而不是功能问题。换句话说,它可以与您选择的任何角色一起工作,但如果它需要做的工作更少,它会更快。
Q
字符进行其他一些替换,那么您需要在打开标记空间之前或关闭标记空间之前Q
将中间文本中表示的所有内容都考虑在内,Qz
或者进行这些替换。通常,您只需选择一个不同的字符作为标记空间的起始字符即可解决此问题。