Como substituir um trecho de texto apenas se ele não aparecer em outro lugar no arquivo?

3

Suponha que eu tenha algum texto como este (saída de 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

Gostaria de substituir o texto como ^ +[0-9a-f]+: pelo número correspondente de espaços (para preservar o tamanho), mas somente se a parte antes de : não for mencionada em nenhum lugar else (como uma palavra, isto é, delimitada por limites de palavras). Por exemplo. No exemplo acima, os rótulos 0 , 2 , 7 , 9 seriam substituídos por um espaço e a permaneceria intacto (desde que seja mencionado na terceira linha).

Veja como o exemplo acima ficaria depois do processamento:

        0f a2                   cpuid  
        a9 01 00 00 00          test   eax,0x1
        74 01                   je     a <myFunc+0xa>
        c3                      ret    
   a:   0f 0b                   ud2a

Existe alguma maneira mais agradável de fazer isso no shell / vim do que contar as ocorrências dos rótulos e depois processar as linhas com base nessas contagens?

Meu código atual processa o arquivo de 2300 linhas em 3 minutos (na CPU Intel Atom), que é muito longo:

#!/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@@@p;q}' "$file"|wc -c)
replacement=$(printf %${labelLength}c ' ')

sed 's@^ \+\([0-9a-f]\+\):.*@@' "$file" | while read label
do
    if [ $(grep -c "\<$label\>" "$file") = 1 ]; then
        sed -i "s@\<$label\>:@$replacement@" "$outfile"
    fi
done
    
por Ruslan 06.04.2016 / 09:41

2 respostas

2

Uma solução 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

Isso é equivalente a:

#!/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";;
}
    
por 06.04.2016 / 10:40
2

Lendo nas entrelinhas, suponho que você queira tornar sua desmontagem mais legível removendo endereços em linhas que não são referidas por instruções de salto e similares. Este awk assume que o número na última coluna, exceto uma, é um endereço quando a última coluna começa "<". Ele lê a desmontagem uma vez, lembrando todos esses endereços em uma matriz e, em seguida, uma segunda vez substituindo o endereço no início da linha, caso não apareça.

$ 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
    
por 06.04.2016 / 10:43