Como substituir apenas a enésima ocorrência de um padrão em um arquivo?

10

Como substituir a terceira ocorrência da string no arquivo usando o comando sed .

Exemplo:

Altere apenas a terceira ocorrência de is para us no arquivo.

Meu arquivo de entrada contém:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Espero que a saída seja:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.
    
por Sureshkumar 29.01.2015 / 11:20

6 respostas

10

É muito mais fácil fazer com perl .

Para alterar a ocorrência de 3 rd :

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

Para alterar cada ocorrência de rd :

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'
    
por 29.01.2015 / 12:52
3

Quando a cadeia de substituição ocorre apenas uma vez por linha, você pode combinar utilitários diferentes.
Quando a entrada está no arquivo "input" e você está substituindo "is" por "us", você pode usar

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'
    
por 29.01.2015 / 12:10
2

O script abaixo (usando GNU sed sintaxe) é utilizável para edição no local e não para saída porque interrompe as linhas de impressão após a substituição desejada:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Se você decidir como chorar você pode modificar acima para

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

que gera todas as linhas

Ou você tem que colocar todas as linhas no espaço padrão (na memória, portanto, tenha cuidado com a limitação de tamanho) e faça a substituição

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file
    
por 29.01.2015 / 11:54
2

Você pode usar sed para isso se as novas linhas forem substituídas por outros caracteres, por exemplo:

tr '\n' '
sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'
0' | sed 's/is/us/3' | tr '
tr '\n' '
sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'
0' | sed 's/is/us/3' | tr '%pre%0' '\n'
0' '\n'

E o mesmo com pure (GNU) sed :

%pre%

( sed substituição de nova linha descaradamente roubada de link )

    
por 29.01.2015 / 11:38
1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/  /'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n//g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) //;P;$d;N;D'

Esse bit de sed carrega apenas uma contagem de is de ocorrências de uma linha para a próxima. Ele deve lidar de forma confiável com o máximo de is es por linha, e não precisa armazenar em buffer linhas antigas enquanto o faz - ele apenas retém um único caractere de nova linha para cada is que encontrar que não é parte de outra palavra.

O resultado é que modificará apenas a terceira ocorrência em um arquivo - e levará contagens por linha. Então, se um arquivo se parece com:

1. is is isis
2. is does

... vai imprimir ...

1. is is isis
2. us does

Ele primeiro lida com casos de borda inserindo um espaço na cabeça e na cauda de cada linha. Isso torna os limites de palavras um pouco mais fáceis de determinar.

Em seguida, ele procura is es válidos inserindo um \n ewline antes de todas as ocorrências de is que imediatamente precedem zero ou um caractere de pontuação seguido por um espaço. Ele faz outra passagem e remove todos os \n ewlines que são imediatamente precedidos por um caractere não-espacial. Esses marcadores deixados para trás corresponderão a is. e is , mas não a this ou ?is .

Em seguida, ele reúne cada marcador na parte final da string. Para cada correspondência de \ni em uma linha, ele anexa um \n ewline à cauda da string e o substitui por i ou u . Se houver 3 \n ewlines em uma linha reunidos na cauda da string, então ele usa o u - else o i. A primeira vez que um u é usado também é o último - o substituto desencadeia um loop infinito que se resume a get line, print line, get line, print line, e assim por diante.

No final de cada ciclo de loop try, ele limpa os espaços inseridos, imprime somente até a primeira linha nova ocorrendo no espaço padrão e vai novamente.

Eu adicionarei um comando l ook na cabeça do loop como:

l; s/\ni(.* )\n{9}/u/...

... e dê uma olhada no que ele faz, pois funciona com essa entrada:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... então aqui está o que faz:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Faz mais sentido talvez com mais is es por linha:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/  /'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n//g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) //'     \
        -e 'P;$d;N;D'
)        

Isso é praticamente a mesma coisa, mas escrito com POSIX BRE e manipulação de argumentos rudimentares.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

... fica ...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... e se eu ativar ${dbg} :

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... podemos assistir iterar ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is
    
por 30.01.2015 / 04:54
0

Aqui está uma solução lógica que usa sed e tr , mas deve ser escrita em um script para funcionar. O código abaixo substitui todas as 3ª ocorrências da palavra especificada no comando sed . Substitua i=3 por i=n para que isso funcione para qualquer n .

Código:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num='grep -o "apple" "output.txt" | wc -l'

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Por que isso funciona:

Suponha que o arquivo de texto seja a b b b b a c a d a b b b a b e b z b s b a b .

  • Quando n = 2: queremos substituir cada segunda ocorrência de b .

    • a b b b b a c a d a b b b a b e b z b s b a b e . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • Primeiro, substituímos a segunda ocorrência, depois a terceira ocorrência, depois a quarta, a quinta e assim por diante. Conte na sequência mostrada acima para ver isso por si mesmo.
  • Quando n = 3: queremos substituir cada terceira ocorrência de b .

    • a b b b b a c a d a b b b a b e b z b s b a b e . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • Primeiro, substituímos a terceira ocorrência, depois a quinta, depois a sétima, a sétima, a décima sétima e assim por diante.
  • Quando n = 4: queremos substituir cada terceira ocorrência de b .

    • Primeiro, substituímos a quarta ocorrência, depois a sétima, depois a décima, a décima terceira e assim por diante.
por 06.03.2018 / 09:34