Encontre uma string e substitua outra string depois que a primeira for encontrada

1

Arquivo:

1
2
3
4
1
5
6
7
4

Eu gostaria de pesquisar uma string, neste caso 1 , e depois alterar a próxima string de 4 para 8 .
Saída esperada:

1
2
3
8
1
5
6
7
4

Eu tentei:

cat file | sed '/1/ s/4/8/'

Mas isso só procura uma string para mudar nessa linha.
Eu também não posso usar o número da linha para substituir no meu arquivo original porque pode haver um número diferente de linhas entre a primeira e a segunda. Eu não tenho o GNU sed instalado.

    
por user1712037 22.01.2018 / 22:20

5 respostas

2

O editor de arquivos especificado por POSIX, ex , é capaz de fazer exatamente isso .

printf '%s\n' '/1//4/s//8/' x | ex file.txt

ex é capaz de combinar vários endereços. Portanto, /1/ significa "Ir para" (ou referir-se a) "a próxima linha que corresponde a regex 1 ." Então, /4/ vai dessa linha para a próxima linha que corresponde a 4 . E s//8/ tem o significado usual; como em Sed, um regex em branco passado para o comando s significa "reutilizar o último regex usado", que neste caso é 4 .

Para imprimir o arquivo modificado, mas não salvar as alterações, use o seguinte comando:

printf '%s\n' '/1//4/s//8/' %p | ex file.txt

Só para melhorar a idéia de vários endereços, o comando a seguir exclui a primeira linha contendo cherry antes da primeira linha contendo banana após linha 27:

printf '%s\n' '27/banana/?cherry?d' x | ex file.txt

x significa salvar as alterações e sair, e %p significa "imprimir arquivo inteiro". ( % é um sinônimo para 1,$ , que é um intervalo de endereços da primeira linha até a última linha.)

    
por 22.01.2018 / 23:42
1

Para substituir apenas o primeiro PATTERN que ocorre após um MARKER , você poderia fazer:

sed '/MARKER/,${
/PATTERN/{
x
//{
x
b
}
g
s/PATTERN/REPLACEMENT/
}
}' infile

Use um intervalo (do 1º MARKER ao final do arquivo) e o buffer de retenção. Toda vez que você encontrar uma linha correspondente a PATTERN , você trocará os buffers e verificará se a linha que estava no espaço de retenção também corresponde: se ocorrer, troque de volta e vá para o final do script; sobrescrever com a linha atual e substituir.

    
por 22.01.2018 / 23:40
1

solução genérica usando awk , considere o seguinte arquivo de entrada modificado com vários 1 s e 4 s

$ cat ip.txt
1
foo
1
xyz
4
4
1
1
eeeee
4
1
rrrrrr
4
1
4

Use um sinalizador para indicar que 1 foi correspondido e um contador para saber qual bloco está sendo modificado. Limpar o sinalizador é necessário para iniciar o ciclo de correspondência novamente

$ # first block
$ awk '/1/{f=1} f && /4/{c++; f=0; if(c==1) $0="8"} 1' ip.txt
1
foo
1
xyz
8
4
1
1
eeeee
4
1
rrrrrr
4
1
4

$ # second block
$ awk '/1/{f=1} f && /4/{c++; f=0; if(c==2) $0="8"} 1' ip.txt
1
foo
1
xyz
4
4
1
1
eeeee
8
1
rrrrrr
4
1
4

pode ser simplificado para alterar apenas o primeiro bloco

awk '/1/{f=1} f && !c && /4/{c++; $0="8"} 1' ip.txt
    
por 23.01.2018 / 05:18
0

Awk solução:

awk '$1==1{ f++ }f==1 && $1==4{ $1=8 }1' file

A saída:

1
2
3
8
1
5
6
7
4
    
por 22.01.2018 / 22:58
0

Com o awk é mais fácil e compatível com qualquer enésima ocorrência necessária

awk '/pattern to search/{n+=1}{if (n==OCCURRENCE){sub("PATTERN","SUBSTITUTE",$0)};print }' FILE-NAME

exemplo:

-bash-4.4$ cat toto
1
2
3
4
5
6
1
2
3
4
5
6
1
2
3
4
-bash-4.4$ awk '/4/{n+=1}{if (n==2){sub("4","---8",$0)};print }' toto
1
2
3
4
5
6
1
2
3
---8
5
6
1
2
3
4
-bash-4.4$ 

apenas o segundo 4 é alterado, mas não o primeiro ou o último.

    
por 22.01.2018 / 23:07

Tags