p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\} \
-e's/(\n*)(.*)/ \2 \1/' \
-e"s/is[$p]?[$s]/\n&/g" \
-e"s/([^$s])\n/\1/g;1G" \
-e:c -e"s/\ni(.* )\n{3}/u\1/" \
-e"/\n$/!s/\n//g;/\ni/G" \
-e's//i/;//tc' \
-e's/^ (.*) /\1/;P;$d;N;D'
Esa parte sedsimplemente lleva un recuento de isocurrencias de una línea a la siguiente. Debe manejar de manera confiable tantos ises por línea como le arroje, y no necesita almacenar las líneas antiguas mientras lo hace; solo retiene un solo carácter de nueva línea por cada iscosa que encuentra, que no es parte de otra palabra.
El resultado es que modificará solo la tercera aparición en un archivo, y llevará los recuentos por línea. Entonces, si un archivo se ve así:
1. is is isis
2. is does
... se imprimirá ...
1. is is isis
2. us does
Primero maneja los casos de borde insertando un espacio en la cabeza y la cola de cada línea. Esto hace que los límites de las palabras sean un poco más fáciles de determinar.
A continuación, busca valores válidos isinsertando una \nlínea de e antes de que todas las apariciones isque preceden inmediatamente a cero o un signo de puntuación seguido de un espacio. Hace otra pasada y elimina todos los \newlines que están precedidos inmediatamente por un carácter que no es espacio. Estos marcadores dejados coincidirán is.y isno, thiso no ?is.
A continuación, reúne cada marcador en la cola de la cadena: para cada \nicoincidencia en una línea, agrega una \nlínea de flecha a la cola de la cadena y la reemplaza con io u. Si hay 3 \newlines en una fila reunida en la cola de la cadena, entonces usa la u, de lo contrario, la i. La primera vez que se usa au también es la última: el reemplazo activa un bucle infinito que se reduce a get line, print line, get line, print line,y así sucesivamente.
Al final de cada ciclo de bucle de prueba, limpia los espacios insertados, imprime solo hasta la primera línea nueva en el espacio del patrón y vuelve a funcionar.
Agregaré un lcomando ook en la parte superior del bucle como:
l; s/\ni(.* )\n{9}/u\1/...
... y eche un vistazo a lo que hace, ya que funciona con esta entrada:
hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged is.
... así que esto es lo que hace:
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
Tiene más sentido quizás con más ises por línea:
nthword()( p='[:punct:]' s='[:space:]'
sed -e '1!{/\n/!b' -e\} \
-e 's/\(\n*\)\(.*\)/ \2 \1/' \
-e "s/$1[$p]\{0,1\}[$s]/\n&/g" \
-e "s/\([^$s]\)\n/\1/g;1G;:c" \
-e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
-e '/\n$/!s/\n//g;/\n'"$1/G" \
-e "s//$1/;//tc" -e 's/^ \(.*\) /\1/' \
-e 'P;$d;N;D'
)
Eso es prácticamente lo mismo, pero escrito con POSIX BRE y manejo de argumentos rudimentarios.
printf 'is is. is? this is%.0s\n' {1..4} | nthword is us 12
... consigue ...
is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is
... y si habilito ${dbg}:
printf 'is is. is? this is%.0s\n' {1..4} |
dbg=1 nthword is us 12
... podemos verlo 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