Substituir string por índice seqüencial

7

Alguém pode sugerir uma maneira elegante de realizar isso?

Entrada:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

a saída deve ser:

test      instant1  ()

test      instant2  ()

test      instant1000()

As linhas vazias estão nos meus arquivos de entrada e há muitos arquivos no mesmo diretório que preciso processar de uma só vez.

Eu tentei isso para substituir muitos arquivos no mesmo diretório e não funcionou.

for file in ./*; do perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

erros:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

e também tentei isto: perl -i -pe 's/instant/$& . ++$n/ge' *.vs

Funcionou, mas o índice continuou incrementando de um para outro arquivo. Eu gostaria de redefinir isso para 1 para o arquivo diff. alguma boa sugestão?

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

funciona, mas substituiu todos os outros arquivos que não devem ser substituídos. Eu prefiro apenas substituir os arquivos com "* .txt" apenas.

    
por user3342338 26.02.2014 / 20:26

2 respostas

11

perl -pe 's/instant/$& . ++$n/ge'

ou com o GNU awk :

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Para editar os arquivos no local, adicione a opção -i a perl :

perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*

Ou recursivamente:

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

Explicações

perl -pe 's/instant/$& . ++$n/ge'

-p é processar a entrada linha por linha, avaliar a expressão passada para -e para cada linha e imprimi-la. Para cada linha, substituímos (usando o s/re/repl/flags operator) instant por si mesmo ( $& ) e o valor incrementado de uma variável ++$n . O g flag é para fazer a substituição globalmente (não apenas uma vez) e e para que a substituição seja interpretada como código perl para valuate (não uma string fixa).

Para edição no local em que uma chamada perl processa mais de um arquivo, queremos que $n seja redefinido em cada arquivo. Em vez disso, usamos $n{$ARGV} (onde $ARGV é o arquivo atualmente processado).

O awk merece um pouco de explicação.

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Estamos usando a capacidade do GNU awk para separar registros em strings arbitrárias (até mesmo expressões regulares). Com -vRS=instant , definimos o r̲ecord s̲eparator como instant . RT é a variável que contém o que foi correspondido por RS , então, normalmente, instant , exceto pelo último registro em que será a sequência vazia. Na entrada acima, os registros ( $0 ) e os terminadores de registro ( RT ) são ( [$0|RT] ):

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

Portanto, tudo o que precisamos fazer é inserir um número incremental no início de cada registro, exceto o primeiro.

O que fazemos acima. Para o primeiro registro, n estará vazio. Nós definimos ORS (o o̲utput r̲ecord s̲eparator ) como RT, de forma que awk imprime n $0 RT . Ele faz isso na segunda expressão ( ++n ) que é uma condição que sempre é avaliada como verdadeira (um número diferente de zero) e, portanto, a ação padrão (de impressão $0 ORS ) é executada para cada registro.

    
por 26.02.2014 / 20:30
4

sed não é realmente a melhor ferramenta para o trabalho, você quer algo com melhores recursos de script. Aqui estão algumas escolhas:

  • perl

    perl -000pe 's/instant/$& . $./e' file 
    

    O -p significa "imprimir todas as linhas" depois de aplicar qualquer script fornecido com -e . O -000 ativa o "modo de parágrafo" para que os registros (linhas) sejam definidos por caracteres de nova linha consecutivos ( \n ), o que permite lidar com linhas espaçadas duplas corretamente. $& é o último padrão correspondido e $. é o número da linha atual do arquivo de entrada. O e em s///e me permite avaliar expressões no operador de substituição.

  • awk (assume que seus dados são exatamente como mostrados, com três campos separados por espaço)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 
    

    Aqui, incrementamos a k variable k apenas se a linha atual não estiver vazia /./ , em cujo caso também imprimimos as informações necessárias. Linhas vazias são impressas como estão.

  • várias conchas

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 
    

    Aqui, cada linha de entrada é dividida automaticamente no espaço em branco e os campos são salvos como $a , $b e $c . Em seguida, dentro do loop, $c é aumentado por um para cada linha para a qual $a não está vazio e seu valor atual é impresso ao lado do segundo campo, $b .

NOTA: todas as soluções acima assumem que todas as linhas no arquivo são do mesmo formato. Se não, a resposta de @Stephane é o caminho a seguir.

Para lidar com muitos arquivos, e supondo que você queira fazer isso com todos arquivos no diretório atual, você pode usar isto:

for file in ./*; do perl -i -000pe 's/instant/$& . $./e' "$file"; done

CUIDADO: Isso pressupõe nomes de arquivo simples sem espaços, se precisar lidar com algo mais complexo, vá para (supondo ksh93 , zsh ou bash ):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -000pe 's/instant/$& . $./e' "$file"
done
    
por 26.02.2014 / 20:33