É 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'
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.
É 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'
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 /'
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
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
:
( sed
substituição de nova linha descaradamente roubada de link )
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
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
.
# 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
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 . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
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 . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
Quando n = 4: queremos substituir cada terceira ocorrência de b
.
Tags text-processing perl sed