![仅当一段文本未出现在文件中的其他位置时,如何替换它?](https://linux22.com/image/87064/%E4%BB%85%E5%BD%93%E4%B8%80%E6%AE%B5%E6%96%87%E6%9C%AC%E6%9C%AA%E5%87%BA%E7%8E%B0%E5%9C%A8%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E5%85%B6%E4%BB%96%E4%BD%8D%E7%BD%AE%E6%97%B6%EF%BC%8C%E5%A6%82%E4%BD%95%E6%9B%BF%E6%8D%A2%E5%AE%83%EF%BC%9F.png)
假设我有一些像这样的文本( 的输出objdump -d
):
0: 0f a2 cpuid
2: a9 01 00 00 00 test eax,0x1
7: 74 01 je a <myFunc+0xa>
9: c3 ret
a: 0f 0b ud2a
我想^ +[0-9a-f]+:
用相应数量的空格替换文本(以保留长度),但是仅有的如果之前的部分:
没有在其他地方提到(作为一个单词,即包含在单词边界中)。例如,在上面的示例中,标签0
, 2
, 7
,9
将被替换为空格并a
保持不变(因为它在第三行中提到)。
下面是上面的例子处理后的样子:
0f a2 cpuid
a9 01 00 00 00 test eax,0x1
74 01 je a <myFunc+0xa>
c3 ret
a: 0f 0b ud2a
在 shell/vim 中是否有比计算标签出现次数然后根据这些计数处理行更好的方法?
我当前的代码在 3 分钟内处理 2300 行文件(在 Intel Atom CPU 上),这太长了:
#!/bin/bash -e
if [ $# -ne 2 ]; then
echo "Usage: $0 infile outfile" >&2
exit 1
fi
file="$1"
outfile="$2"
cp "$file" "$outfile"
labelLength=$(sed -n '/^ \+\([0-9a-f]\+\):.*/{s@@\1@p;q}' "$file"|wc -c)
replacement=$(printf %${labelLength}c ' ')
sed 's@^ \+\([0-9a-f]\+\):.*@\1@' "$file" | while read label
do
if [ $(grep -c "\<$label\>" "$file") = 1 ]; then
sed -i "s@\<$label\>:@$replacement@" "$outfile"
fi
done
答案1
Perl 解决方案:
$ perl -lne '$k{"$_:"}++ for split(/\b/); push @l,$_; }{
map{s/\S+:/$k{$&}<2 ? " " x length($&) : $&/e; print}@l;' file
0f a2 cpuid
a9 01 00 00 00 test eax,0x1
74 01 je a <myFunc+0xa>
c3 ret
a: 0f 0b ud2a
这相当于:
#!/usr/bin/perl
use strict;
my %wordsHash;
my @lines;
## Read the input file line by line, saving each
## line as $_. This is what 'perl -n` means.
while (<>) {
## Remove trailing newlines. This is done
## by -l in the one liner.
chomp;
## Split the current line on word boundaries
my @wordsArray=split(/\b/);
## Save each word + ":" as a key in the hash %wordsHash,
## incrementing the value by one each time the word
## is seen.
foreach my $word (@wordsArray) {
$wordsHash{"$word:"}++;
}
## Add the line to the array @lines
push @lines, $_;
}
## After the file has been read. ('}{' in the one-liner)
## Iterate over each line in @lines ( map{}@l in the one-liner)
foreach my $line (@lines) {
## Grab the first set of non-whitespace
## characters until the 1st ':'
$line=~/\S+:/;
## $& is whatever was matched
my $match=$&;
## If the match was seen only once
## as a word (all will be seen at least once)
if ($wordsHash{$match}<2) {
## The replacement is as many spaces
## as $match has characters.
my $rep = " " x length($match);
## Replace it in the line
$line=~s/$match/$rep/;
}
## Print the line
print "$line\n";;
}
答案2
阅读字里行间,我假设您希望通过删除跳转和类似指令未引用的行上的地址来使反汇编更具可读性。当最后一列以“<”开始时,该 awk 假定最后一列中的数字是地址。它读取反汇编一次,记住数组中的所有此类地址,然后第二次替换行开头的地址(如果它没有出现)。
$ objdump -d /bin/ls >/tmp/a
$ awk '
NR==FNR { if($NF ~ /</) address[$(NF-1)] = 1; next }
$1 ~ /:/ { if(!address[substr($1,1,length($1)-1)]){
i = index($0,":")
printf "%*s%s\n",i," ",substr($0,i+1)
next }
}
{ print }
' /tmp/a /tmp/a