Cómo eliminar la línea si contiene un carácter exactamente una vez

10

Quiero eliminar una línea de un archivo que contiene un carácter en particular solo una vez, si está presente más de una vez o no está presente, entonces mantenga la línea en el archivo.

Por ejemplo:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Aquí, el carácter que quiero eliminar es Casí, el comando debería eliminar líneas FGTHDCy JUTDYCporque tienen Cexactamente una vez.

¿Cómo puedo hacer esto usando sedo awk?

Namz
fuente

Respuestas:

20

En awkpuede establecer el separador de campo a cualquier cosa. Si lo configura en C, entonces tendrá tantos campos +1 como ocurrencias C.

Entonces, si dices awk -F'C' '{print NF}' <<< "C1C2C3"que obtienes 4: CCCconsiste en 3 Cs, y por lo tanto 4 campos.

Desea eliminar líneas en las que Cocurre exactamente una vez. Teniendo esto en cuenta, en su caso querrá eliminar aquellas líneas en las que hay exactamente dos Ccampos. Así que solo sáltelos:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD
fedorqui
fuente
44
¡Uso astuto del awkseparador de campo!
Valentin B.
interesante, como en el caso predeterminado (FS = "") ignora los espacios iniciales ($ 1 = el primer no espacio en la línea) y también las repeticiones (puede tener 5 espacios para separar el campo 1 y el campo 2) ... espacio es probablemente tratado especialmente? (para verlo, uno puede hacerlo awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'y alimentarlo con algunas líneas, algunas con múltiples spces y otras comenzando con espacios)
Olivier Dulac
2
@OlivierDulac, sí, el espacio se maneja especialmente según lo especificado por POSIX .
Comodín
8

enfoque sed :

sed -i '/^[^C]*C[^C]*$/d' input

-i la opción permite la modificación de archivos en el lugar

/^[^C]*C[^C]*$/- coincide con líneas que contienen Csolo una vez

d - eliminar líneas coincidentes

RomanPerekhrest
fuente
8

Esto se puede hacer con sed:

Código:

sed '/C.*C/p;/C/d' file1

Resultados:

DTHGTY
HYTRHD
HTCCYD

¿Cómo?

  1. Une e imprime cualquier línea con al menos dos copias de Cvia/C.*C/p
  2. Elimine cualquier línea con una Cvía /C/d, esto incluye las líneas ya impresas en el paso 1
  3. Impresión predeterminada del resto de las líneas
Stephen Rauch
fuente
2
Enfoque alternativo inteligente; Me gusta.
Comodín
6

Esto elimina las líneas con exactamente una aparición de C.

grep -v '^[^C]*C[^C]*$' file

La expresión regular [^C]coincide con un carácter que no es C (o nueva línea), y el operador de repetición (también conocido como estrella de Kleene) *especifica cero o más repeticiones de la expresión anterior.

La salida predeterminada de grep(y la mayoría de las otras herramientas orientadas al texto) es la salida estándar; redirigir a un nuevo archivo y tal vez moverlo sobre el archivo original si eso es lo que desea. Se puede utilizar la misma expresión regular sed -ipara la edición in situ:

sed -i '/^[^C]*C[^C]*$/d' file

(En algunas plataformas, especialmente * BSD, incluido macOS, la -iopción requiere un argumento, como -i ''.)

tripleee
fuente
1
sed -i '/^[^C]*C[^C]*$/d' file- Parece que fue publicado antes, ¿cómo crees que es el plagio?
RomanPerekhrest
1
De hecho, hay alguna duplicación. Comencé con la greprespuesta, pero obviamente se extiende fácilmente a la sed -ivariante. No vi su respuesta porque estaba buscando greprespuestas anteriores .
tripleee
1
Es más seguro claramente evitar precisamente -icon sedy en lugar de redirigir a un archivo nuevo y reemplazar el original con que si la sedutilidad salió sin error.
Kusalananda
2
Ogrep -vx '[^C]*C[^C]*'
Stéphane Chazelas,
@Kusalananda Pero entonces también podrías usarlo grepporque es más claro y más robusto (en particular, sedtiene un código de salida menos informativo).
tripleee
4

La herramienta POSIX para ediciones guionadas de un archivo (en lugar de imprimir los contenidos modificados a la salida estándar) es ex.

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Por supuesto, puede usarlosed -i si su versión de Sed lo admite, solo tenga en cuenta que no es portátil si está escribiendo un script destinado a ejecutarse en diferentes tipos de sistemas.


David Foerster preguntó en los comentarios:

¿Hay alguna razón por la que estás usando printfy no echoo algo así ex -c COMMAND?

Respuesta: sí.

Para printfvs. echoes una cuestión de portabilidad; ver ¿Por qué es printf mejor que echo? Y también es más fácil intercalar líneas nuevas entre comandos usando printf.

Para printf ... | exvs. ex -c ..., es una cuestión de manejo de errores. Para este comando específico no importaría, pero en general sí; por ejemplo, intenta poner

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

en un guion Contraste con lo siguiente:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

El primero colgará y esperará entrada; el segundo saldrá cuando el excomando reciba EOF , por lo que el script continuará. Existen soluciones alternativas, como s///ePOSIX, pero no están especificadas. Prefiero usar el formulario portátil, que se muestra arriba.

Para el gcomando, debe haber una nueva línea al final, y prefiero usar printfpara ajustar los comandos en lugar de incrustar una nueva línea entre comillas simples.

Comodín
fuente
1
¿Hay alguna razón por la que estás usando printfy no echoo algo así ex -c COMMAND?
David Foerster
@DavidFoerster, sí. Comencé a responderte en comentarios pero creció mucho, así que lo agregué a la respuesta.
Comodín
Gracias y +1! Sabía acerca de printfvs. echo(aunque generalmente prefiero echocuando el argumento está codificado) pero no lo he usado exampliamente hasta ahora.
David Foerster
2

Aquí hay un par de opciones con perl.

Como solo está haciendo coincidir un solo carácter, puede usar tr/C//(una traducción, sin reemplazos), para devolver el número de coincidencias de C:

perl -lne 'print if tr/C// != 1' file

En términos más generales, si desea hacer coincidir una cadena de caracteres múltiples o una expresión regular, puede usar esto:

perl -lne 'print if (@m = /C/g) != 1' file

Esto asigna las coincidencias de la expresión regular /C/ga una lista @me imprime líneas cuando la longitud de esa lista no lo es 1.

El -iinterruptor se puede agregar para editar "en el lugar".

Tom Fenech
fuente
2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

fuente
Tenga en cuenta que asume que GNU sed, t #...normalmente se ramificaría a la etiqueta llamada #...en la mayoría de las otras sedimplementaciones.
Stéphane Chazelas
Incluso el !bes GNU sed ya que a la rama no le gusta nada excepto una etiqueta o una nueva línea después.
Sí, b, t, :, }(y r file, w file...) no puede tener un comando después de ellos en la misma línea. También puede usar -eopciones separadas .
Stéphane Chazelas
Su opción perl no produce la salida correcta. Supongo que olvidaste agregar el gmodificador.
Tom Fenech
@TomFenech Tienes razón. Estoy arreglando eso. Gracias.
1

Para cualquiera que quiera awkespecíficamente, ofrecería

awk '/C[^C]*C/{next}//{print}'

omita la línea si coincide con el patrón, imprímalo de lo contrario. En realidad no es necesario {print}, puede usar una //impresión predeterminada, pero creo que está más claro.

Mi primer pensamiento fue usar egrep -vcon el mismo patrón, pero eso en realidad no responde a la pregunta planteada.

nigel222
fuente
1
¿Cuál es el punto de igualar algo después {next}? Simplemente diga awk '/pattern/ {next} 1'y se imprimirán todas las líneas que no coincidan con el patrón. O, mejor, awk '!/pattern/'imprimirlos directamente.
fedorqui
@fedorqui buen punto sobre !/pattern/(que de alguna manera se me olvidó ) pero preferiría ver un autoexplicativo //{print}que un críptico 1. Asuma la menor competencia y fluidez de la siguiente persona para mantener su código, de manera consistente con no hacerlo seriamente menos eficiente o efectivo.
nigel222