Quiero reemplazar solo las primeras k
instancias de una palabra.
¿Cómo puedo hacer esto?
P.ej. Dicho archivo foo.txt
contiene 100 instancias de la palabra 'linux'.
Necesito reemplazar las primeras 50 ocurrencias solamente.
Quiero reemplazar solo las primeras k
instancias de una palabra.
¿Cómo puedo hacer esto?
P.ej. Dicho archivo foo.txt
contiene 100 instancias de la palabra 'linux'.
Necesito reemplazar las primeras 50 ocurrencias solamente.
Respuestas:
La primera sección a continuación describe el uso sed
para cambiar las primeras k-ocurrencias en una línea. La segunda sección amplía este enfoque para cambiar solo las primeras k-ocurrencias en un archivo, independientemente de en qué línea aparezcan.
Con sed estándar, hay un comando para reemplazar la k-ésima aparición de una palabra en una línea. Si k
es 3, por ejemplo:
sed 's/old/new/3'
O bien, uno puede reemplazar todas las ocurrencias con:
sed 's/old/new/g'
Ninguno de estos es lo que quieres.
GNU sed
ofrece una extensión que cambiará la k-ésima ocurrencia y todo después de eso. Si k es 3, por ejemplo:
sed 's/old/new/g3'
Estos se pueden combinar para hacer lo que quieras. Para cambiar las 3 primeras ocurrencias:
$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old
donde \n
es útil aquí porque podemos estar seguros de que nunca ocurre en una línea.
Utilizamos tres sed
comandos de sustitución:
s/\<old\>/\n/g4
Esta es la extensión GNU para reemplazar el cuarto y todos los sucesos posteriores de old
con \n
.
La función de expresión regular extendida \<
se usa para hacer coincidir el comienzo de una palabra y \>
para hacer coincidir el final de una palabra. Esto asegura que solo las palabras completas coincidan. La expresión regular extendida requiere la -E
opción sed
.
s/\<old\>/new/g
Solo quedan las tres primeras ocurrencias old
y esto las reemplaza a todas new
.
s/\n/old/g
El cuarto y todos los sucesos restantes de old
fueron reemplazados por \n
en el primer paso. Esto los devuelve a su estado original.
Si GNU sed no está disponible y desea cambiar las primeras 3 apariciones de old
a new
, utilice tres s
comandos:
$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old
Esto funciona bien cuando k
es un número pequeño pero se escala deficiente a grande k
.
Dado que algunos seds que no son GNU no admiten la combinación de comandos con punto y coma, cada comando aquí se presenta con su propia -e
opción. También puede ser necesario verificar que sed
admite los símbolos de límite de palabras, \<
y \>
.
Podemos decirle a sed que lea todo el archivo y luego realice las sustituciones. Por ejemplo, para reemplazar las tres primeras ocurrencias del old
uso de un sed de estilo BSD:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
Los comandos sed H;1h;$!d;x
leen todo el archivo.
Como lo anterior no utiliza ninguna extensión GNU, debería funcionar en sed BSD (OSX). Tenga en cuenta, pensó, que este enfoque requiere un sed
que puede manejar largas colas. GNU sed
debería estar bien. Aquellos que usan una versión de GNU no sed
deben probar su capacidad para manejar largas colas.
Con un GNU sed, podemos usar el g
truco descrito anteriormente, pero con \n
reemplazado por \x00
, para reemplazar los primeros tres casos:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
Este enfoque se escala bien y se k
hace grande. Sin embargo, esto \x00
supone que no está en su cadena original. Dado que es imposible poner el carácter \x00
en una cadena bash, esto generalmente es una suposición segura.
tr '\n' '|' < input_file | sed …
. Pero, por supuesto, eso convierte toda la entrada en una línea, y algunos seds que no son GNU no pueden manejar líneas arbitrariamente largas. (2) Usted dice: "... arriba, la cadena entre comillas '|'
debe reemplazarse por cualquier carácter, o cadena de caracteres, ..." Pero no puede usar tr
para reemplazar un carácter con una cadena (de longitud> 1). (3) En tu último ejemplo, dices -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
. Esto parece ser un error tipográfico para -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
.
Los comandos awk se pueden usar para reemplazar las primeras N apariciones de la palabra con el reemplazo.
Los comandos solo reemplazarán si la palabra es una coincidencia completa.
En los ejemplos a continuación, estoy reemplazando las primeras 27
apariciones de old
connew
Usando sub
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
Este comando recorre cada campo hasta que coincide
old
, comprueba que el contador está por debajo de 27, aumenta y sustituye la primera coincidencia en la línea. Luego se mueve al siguiente campo / línea y se repite.
Reemplazar el campo manualmente
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Similar al comando anterior pero como ya tiene un marcador en qué campo depende
($i)
, simplemente cambia el valor del campo deold
anew
.
Realizar un chequeo antes
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Comprobar que la línea contiene elementos viejos y que el contador está por debajo de 27
SHOULD
proporciona un pequeño aumento de velocidad, ya que no procesará líneas cuando sean falsas.
RESULTADOS
P.ej
old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old
a
new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old
Supongamos que desea reemplazar solo las primeras tres instancias de una cadena ...
seq 11 100 311 |
sed -e 's/1/\
&/g' \ #s/match string/\nmatch string/globally
-e :t \ #define label t
-e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{' \ #if not 3 characters in hold space do
-e 's/$/./' \ #add a new char to hold space
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #branch back to label t
-e '};x' \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g' #end match function; remove all newline characters
nota: lo anterior probablemente no funcionará con comentarios incrustados
... o en mi caso de ejemplo, de un '1' ...
22
211
211
311
Allí uso dos técnicas notables. En primer lugar, cada aparición de 1
una línea se reemplaza por \n1
. De esta manera, como hago los reemplazos recursivos a continuación, puedo estar seguro de no reemplazar la ocurrencia dos veces si mi cadena de reemplazo contiene mi cadena de reemplazo. Por ejemplo, si lo reemplazo he
con hey
él, aún funcionará.
Hago esto como:
s/1/\
&/g
En segundo lugar, estoy contando los reemplazos agregando un personaje al h
espacio antiguo para cada ocurrencia. Una vez que llegue a tres, no ocurrirán más. Si aplica esto a sus datos y cambia los \{3\}
reemplazos totales que desea y las /\n1/
direcciones a lo que quiera reemplazar, debe reemplazar solo los que desee.
Solo hice todas las -e
cosas para facilitar la lectura. POSIXly Podría escribirse así:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
Y con GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
Recuerde también que sed
está orientado a líneas: no se lee en todo el archivo y luego intenta volver a recorrerlo, como suele ser el caso en otros editores. sed
Es simple y eficiente. Dicho esto, a menudo es conveniente hacer algo como lo siguiente:
Aquí hay una pequeña función de shell que lo agrupa en un comando simplemente ejecutado:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
Entonces con eso puedo hacer:
seq 11 100 311 | firstn 7 1 5
...y obten...
55
555
255
311
...o...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...Llegar...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... o, para que coincida con su ejemplo (en un orden de magnitud menor) :
yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux
Una alternativa corta en Perl:
perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
Cambie el valor de `$ n $ a su gusto.
Cómo funciona:
new
por old
( s/old/new/
) y siempre que se pueda, se incrementa la variable $i
( ++$i
).1 while ...
) siempre y cuando haya realizado menos $n
sustituciones en total y puede realizar al menos una sustitución en esa línea.Use un bucle de concha y ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Sí, es un poco tonto.
;)
Nota: Esto puede fallar si hay menos de 50 instancias old
en el archivo. (No lo he probado). Si es así, dejaría el archivo sin modificar.
Mejor aún, usa Vim.
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
Explicación:
q # Start recording macro
q # Into register q
gg # Go to start of file
/old<CR> # Go to first instance of 'old'
:s/old/new/<CR> # Change it to 'new'
q # Stop recording
49@q # Replay macro 49 times
:x # Save and exit
Una solución simple pero no muy rápida es recorrer los comandos descritos en /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -archivo
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
Este comando sed en particular probablemente solo funcione para GNU sed y si newword no es parte de oldword . Para sed no GNU, vea aquí cómo reemplazar solo el primer patrón en un archivo.
Con GNU awk
puede establecer el separador de registros RS
para la palabra que se reemplazará delimitada por límites de palabras. Entonces se trata de establecer el separador de registros en la salida a la palabra de reemplazo para los primeros k
registros mientras se conserva el separador de registros original para el resto
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
O
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file