Como usar o comando sed para modificar a linha de entrada de um arquivo por linha no shell script?

3

Este é o meu file1 :

#$BQ
#{ VOL       @home    }
#database    daba
#relation    tcdeatid
#copy           1
#{ version        0 }
#opendb
#clear
#.lruno := 72
#.infno := 1
#.tid.noel := 101
#.tid.info := 64
#.tid.setnr := 1225              <--- (1225 number will be changed)
#.typeidm := 1
#.sourcetable := 2
#writedb     
#clear
#.lruno := 72
#.infno := 205
#.tid.noel := 101
#.tid.info := 76
#.tid.setnr := 5625              <--- (5625 number will be changed)
#.typeidm := 1
#.sourcetable := 2
#writedb
#$EOJ

Em file2 , tenho as entradas corretas:

#.tid.info := 3345              <--- (I want to put this number in file1 in place of 64)
#.tid.setnr := 1254              <--- (I want to put this number in file1 in place of 1225)
#.tid.info := 5567              <--- (I want to put this number in file1 in place of 76)
#.tid.setnr := 9056              <--- (I want to put this number in file1 in place of 5625)

Eu quero fazer alterações em file1 , direcionadas por dados de file2 .

Eu tentei sed "s/tid.setnr := 1225/tid.setnr := 1254/g" file1 > modified_file1

Mas, em vez de fazê-lo manualmente, quero que meu script leia os novos valores tid.info e tid.setnr de file2 e modifique as respectivas linhas em file1 . A primeira linha em file2 deve substituir toda a primeira linha em file1 que contém tid.info , a segunda linha em file2 deve substituir a segunda linha em file1 que contém tid.setnr e assim por diante.

    
por pmaipmui 06.06.2015 / 05:28

4 respostas

3

Uma maneira de fazer isso com sed é gerar um script sed :

tmpfile=/tmp/Nainita.$$
exec 3< file2
grep -n "tid\.setnr" file1 | while IFS=: read n junk
do
        IFS= read -r line <&3  ||  break
        printf "%sc%s\n%s\n" "$n" '\' "$line"
done > "$tmpfile"
exec 3<&-
sed -f "$tmpfile" file1 > modified_file1
rm -f "$tmpfile"
  • exec 3< file2 abre file2 para ler no descritor de arquivo 3.
  • O grep -n encontra as linhas em file1 que contêm tid.setnr , por número de linha. Isso é canalizado em um loop while .
  • while IFS=: read n junk

    • while … read … significa ler uma linha por vez, repetidamente contanto que haja informações para ler (ou seja, parando quando chegar ao final dos dados).
    • IFS=: read n junk significa ler tudo até o primeiro : (que será o número da linha de grep -n ) em n , e o resto da linha (a antiga linha de dados tid.setnr ) em junk , que ignoramos, porque não nos importamos com isso.
  • IFS= read -r line <&3 lê uma linha do descritor de arquivo 3 ( file2 ), fazendo tudo o que sabemos fazer para dizer ao shell para não rasgá-lo, e colocá-lo em uma variável chamada line .
  • … || break diz, se o read acima falhar (presumivelmente devido ao final do arquivo), sair do loop.
  • O comando printf escreve um comando sed c hange, endereçado para o número de linha n , dizendo que essa linha deve ser substituída com o conteúdo na variável line .
  • done > "$tmpfile" marca o final do ciclo while e especifica que a saída padrão de todo o loop é $tmpfile . Isso terminará assim:

    13c\
    #.tid.setnr := 1254
    22c\
    #.tid.setnr := 9056
    • Observe que o loop while termina assim que recebe um EOF de qualquer entrada. Portanto, se file1 tiver mais tid.setnr linhas que file2 , os extras ficarão inalterados (isto é, os comandos c para eles não serão gerados). Da mesma forma, se file2 tiver mais tid.setnr linhas que file1 , os extras serão ignorados. Infelizmente, esta discrepância não será relatada, embora adicionar essa capacidade não seria muito difícil.
  • exec 3<&- fecha o descritor de arquivo 3.

  • sed -f "$tmpfile" file1 > modified_file1 corre sed em file1 , lendo comandos de $tmpfile e escrevendo saída para modified_file1 .

Isso deve fazer o que você quiser. Obviamente, mude os nomes dos arquivos, se quiser. Você deve executar isso “como está” uma vez e, em seguida, examinar o arquivo modified_file1 (ou simplesmente altere o comando sed para não redirecionar sua saída, por isso, escreve na tela) e verifique se a saída é o que você deseja. Em seguida, você pode alterar o comando sed para sed -i para editar file1 no lugar (se é isso que você quer fazer).

Se o comando sed der um erro como "Número excessivo de linhas", tente dividir o arquivo de script ( $tmpfile ) em arquivos menores. Eu sugiro começar com um tamanho abaixo de 100 comandos; por exemplo, 90 comandos, que são 180 linhas, pois cada comando é de duas linhas. Você pode fazer isso manualmente, com um editor de texto 1 , mas há uma ferramenta escrita especificamente para este trabalho. É, intuitivamente, chamado split . O comando

split --lines=180 "$tmpfile"

dividirá o script em arquivos no diretório atual chamado xaa , xab , xac ,…. O primeiro n −1 terá 180 linhas de comprimento; o último será o que for necessário (≤ 180) para perfazer o total. Por exemplo, se você tiver 500 instâncias de tid.setnr , então o seu script terá 1000 linhas de comprimento e você terá 6% de arquivosx - xaa , xab , xac , xad e xae terão 180 linhas, e xaf terá 100. Agora, faça

sed -f xaa file1 > modified_file1aa

Se você ainda tiver "muitos números de linha", volte e tente novamente com um número menor de --lines . Se ele não informar um erro, veja modified_file1aa e veja se parece que as primeiras 90% das linhastid.setnr foram alteradas. Se parece OK, então faça

sed -f xab modified_file1aa > modified_file1ab
sed -f xac modified_file1ab > modified_file1ac
sed -f xad modified_file1ac > modified_file1ad
sed -f xae modified_file1ad > modified_file1ae
sed -f xaf modified_file1ae > modified_file1af

modified_file1af agora é seu% finalmodified_file1.

Se você quiser experimentar play , você pode

  • Tente números maiores de --lines até descobrir o que é o máximo.
  • Tente

    sed -f xaa -f xab -f xac -f xad -f xae -f xaf file1 > modified_file1_test
    

    mas isso provavelmente não funcionará.

  • Se você fizer as experiências acima, incentive você a nos informar os resultados.

Se você só precisa fazer isso uma vez, está feito. Mas, se você precisar fazer isso repetidamente, altere as duas últimas linhas do bloco de código no topo desta resposta para

split --lines=180 "$tmpfile"            <--- (Using a number that works for you, of course)
cp file1 modified_file1
for f in x*
do
        sed -i -f "$f" modified_file1
done
rm -f "$tmpfile" x*

Como mencionei anteriormente, a opção -i informa sed para editar o arquivo especificado no lugar (ou seja, escreva as alterações de volta no arquivo de entrada). Ou, ainda mais simples,

split --lines=180 "$tmpfile"
for f in x*
do
        sed -i -f "$f" file1
done
rm -f "$tmpfile" x*

se você não precisar manter o original file1 intacto.

Observe que a sinopse de uso para split é

split [OPTION]… [INPUT [PREFIX]]

e seu comportamento padrão é criar arquivos chamados PREFIXaa , PREFIXab , PREFIXac etc. Em outras palavras, o padrão PREFIX é x . Se você tiver outros arquivos cujos nomes começam com x , você deve definir prefix para ser algo que será único (por exemplo, prefix=Nainita.$$. ou prefix=/tmp/Nainita.$$. ) e depois mude o acima para

split --lines=180 "$tmpfile" "$prefix"
for f in "$prefix"*
do
        sed -i -f "$f" file1
done
rm -f "$tmpfile" "$prefix"*

por 06.06.2015 / 07:51
4

Com o comando R do GNU sed:

sed -e 's/^#.tid.setnr :=.*//;tA;b;:A;R file2' -e 'd' file1
    
por 06.06.2015 / 09:13
1
{   sed '/^#\.tid\.setnr/!d;=;g;G' |
    paste  -d'c\\n' - - - ./addfile
}   <./mainfile | sed -f - ./mainfile

Ele apenas preenche os números de linha relevantes de ./ mainfile para cada linha em ./ addfile como separados pela string de comando c\ e ; newline > antes de passar os resultados para outro sed como um script para editar ./ mainfile . Dados seus dados de exemplo, o script gerado se parece com:

13c\
#.tid.setnr := 1254              <--- (I want to put this number in file1 in place of 1225)
22c\
#.tid.setnr := 9056              <--- (I want to put this number in file1 in place of 5625)

... que instrui sed a c hange lines 13 e 22 de sua saída para corresponder às linhas anexadas de cada comando.

    
por 06.06.2015 / 14:05
0

Eu sou encontrado em perl. Este script é um script que parece resolver o problema:

#!/usr/bin/perl
#
use strict;

# subroutine to load a test file into an array
sub load{
   my $file = shift;
   open my $in, "<", $file or die "unable to open $file : $!";
   my @data = <$in>;
   chomp @data;
   foreach (@data) { s/\cM//g;}
   return @data;
}


my $file1 = shift || die "usage: $0 [file1] [file2]\n";
my @file1_data = &load($file1);
my $file2 = shift || die "usage: $0 [file1] [file2]\n";
my @file2_data = &load($file2);

my $i = 0;
foreach( @file1_data )
{
    if( $i < @file2_data ) 
    {
        my @s = split / /, $file2_data[$i];
        if( @s )
        {
            if( /^$s[0]/ )
            {
            $_ = $file2_data[$i++];
            }
        }
    }
    print "$_\n";
    
por 17.07.2015 / 22:19