¿Cómo eliminar las líneas que aparecen en el archivo B de otro archivo A?

160

Tengo un archivo grande A (que consta de correos electrónicos), una línea para cada correo. También tengo otro archivo B que contiene otro conjunto de correos.

¿Qué comando usaría para eliminar todas las direcciones que aparecen en el archivo B del archivo A.

Entonces, si el archivo A contiene:

A
B
C

y el archivo B contenía:

B    
D
E

Entonces el archivo A debe quedar con:

A
C

Ahora sé que esta es una pregunta que podría haberse hecho con más frecuencia, pero solo encontré un comando en línea que me dio un error con un delimitador incorrecto.

Cualquier ayuda sería muy apreciada! Alguien seguramente obtendrá una ingeniosa frase, pero yo no soy el experto en shell.

slhck
fuente
1
La mayoría si las respuestas aquí son para archivos ordenados, y falta la más obvia, lo que por supuesto no es tu culpa, pero eso hace que la otra sea más útil en general.
tripleee

Respuestas:

202

Si los archivos están ordenados (están en su ejemplo):

comm -23 file1 file2

-23suprime las líneas que están en ambos archivos, o solo en el archivo 2. Si los archivos no están ordenados, canalícelos sortprimero ...

Vea la página del manual aquí

El arquetipo de Pablo
fuente
8
comm -23 file1 file2 > file3generará contenido en el archivo1 no en el archivo2, al archivo3. Y luego mv file3 file1finalmente borraría los contenidos redundantes en el archivo1.
Espectral
2
Alternativamente, use comm -23 file1 file2 | sponge file1. No se necesita limpieza.
Socowi
El enlace de la página de manual
Felix Rabe
@Socowi ¿Qué es la esponja? No tengo eso en mi sistema. (macos 10.13)
Felix Rabe
@FelixRabe, bueno, eso es agotador. Reemplazado con su enlace. Gracias
The Archetypal Paul
84

grep -Fvxf <lines-to-remove> <all-lines>

  • funciona en archivos no ordenados
  • mantiene el orden
  • es POSIX

Ejemplo:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Salida:

b
a
01
b

Explicación:

  • -F: use cadenas literales en lugar del BRE predeterminado
  • -x: solo considera las coincidencias que coinciden con la línea completa
  • -v: imprimir no coincidente
  • -f file: tomar patrones del archivo dado

Este método es más lento en archivos previamente ordenados que otros métodos, ya que es más general. Si la velocidad también importa, vea: Forma rápida de encontrar líneas en un archivo que no están en otro?

Aquí hay una automatización rápida de bash para la operación en línea:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub aguas arriba .

uso:

remove-lines lines-to-remove remove-from-this-file

Ver también: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
55

¡Awk al rescate!

Esta solución no requiere entradas ordenadas. Primero debe proporcionar el archivo B.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

devoluciones

A
C

¿Como funciona?

NR==FNR{a[$0];next} El modismo es para almacenar el primer archivo en una matriz asociativa como claves para una prueba posterior "contiene".

NR==FNR está verificando si estamos escaneando el primer archivo, donde el contador de línea global (NR) es igual al contador de línea de archivo actual (FNR).

a[$0] agrega la línea actual a la matriz asociativa como clave, tenga en cuenta que esto se comporta como un conjunto, donde no habrá valores duplicados (claves)

!($0 in a)ahora estamos en el (los) archivo (s) siguiente (s), ines una prueba contiene, aquí está verificando si la línea actual está en el conjunto que poblamos en el primer paso del primer archivo, !niega la condición. Lo que falta aquí es la acción, que por defecto es {print}y generalmente no está escrita explícitamente.

Tenga en cuenta que esto ahora se puede utilizar para eliminar palabras en la lista negra.

$ awk '...' badwords allwords > goodwords

Con un ligero cambio, puede limpiar varias listas y crear versiones limpias.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...
karakfa
fuente
marcas completas en esto. Para usar esto en la línea de comando en GnuWin32 en Windows, reemplace los nibbles simples con comillas dobles. funciona de maravilla. muchas gracias.
twobob
Esto funciona, pero ¿cómo podré redirigir la salida al archivo A en forma de A (con una nueva línea) B
Anand Builders
Supongo que te refieres a A\nCescribir primero en un archivo temporal y sobrescribir el archivo original... > tmp && mv tmp fileA
karakfa
Un sobresaliente en esto de mí también. Este awk tarda todo 1 segundo en procesar un archivo con 104,000 entradas: +1:
MitchellK
Al usar esto en scripts, asegúrese de verificar primero que fileBno esté vacío (0 bytes de longitud), porque si lo es, obtendrá un resultado vacío en lugar del contenido esperado de fileA. (Causa: FNR==NRse aplicará a fileAentonces.)
Peter Nowee
18

Otra forma de hacer lo mismo (también requiere una entrada ordenada):

join -v 1 fileA fileB

En Bash, si los archivos no están ordenados previamente:

join -v 1 <(sort fileA) <(sort fileB)
Pausado hasta nuevo aviso.
fuente
7

Puede hacer esto a menos que sus archivos estén ordenados

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formates para líneas que están en el archivo b pero no en a --old-..es para líneas que están en el archivo a pero no en b --unchanged-..es para líneas que están en ambos. %Lhace que la línea se imprima exactamente.

man diff

para más detalles

aec
fuente
1
Dices que esto funcionará a menos que los archivos estén ordenados. ¿Qué problemas ocurren si se ordenan? ¿Qué pasa si están parcialmente ordenados?
Carlos Macasaet
1
Eso fue en respuesta a la solución anterior que sugirió el uso del commcomando. commrequiere que los archivos estén ordenados, por lo que si están ordenados, también puede usar esa solución. Sin embargo
aec
7

Este refinamiento de la buena respuesta de @karakfa puede ser notablemente más rápido para archivos muy grandes. Al igual que con esa respuesta, ninguno de los archivos necesita ser ordenado, pero la velocidad está asegurada en virtud de las matrices asociativas de awk. Solo el archivo de búsqueda se guarda en la memoria.

Esta formulación también permite la posibilidad de que solo se use un campo particular ($ N) en el archivo de entrada en la comparación.

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(Otra ventaja de este enfoque es que es fácil modificar el criterio de comparación, por ejemplo, recortar los espacios en blanco iniciales y finales).

pico
fuente
Esto es más difícil de usar en un escenario de plataforma cruzada de caja de esquina que el otro revestimiento. Sin embargo
felicitaciones
2

Puedes usar Python:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'
Hola Adios
fuente
2

Puedes usar - diff fileA fileB | grep "^>" | cut -c3- > fileA

Esto funcionará para archivos que no están ordenados también.

Darpan
fuente
-1

Para eliminar líneas comunes entre dos archivos, puede usar el comando grep, comm o join.

grep solo funciona para archivos pequeños. Use -v junto con -f.

grep -vf file2 file1 

Esto muestra líneas del archivo1 que no coinciden con ninguna línea del archivo2.

comm es un comando de utilidad que funciona en archivos ordenados léxicamente. Toma dos archivos como entrada y produce tres columnas de texto como salida: líneas solo en el primer archivo; líneas solo en el segundo archivo; y líneas en ambos archivos. Puede suprimir la impresión de cualquier columna utilizando la opción -1, -2 o -3 en consecuencia.

comm -1 -3 file2 file1

Esto muestra líneas del archivo1 que no coinciden con ninguna línea del archivo2.

Finalmente, hay join, un comando de utilidad que realiza una unión de igualdad en los archivos especificados. Su opción -v también permite eliminar líneas comunes entre dos archivos.

join -v1 -v2 file1 file2
Aakarsh Gupta
fuente
Todo esto ya se dio en otras respuestas. Tu grep necesita un -F, o obtendrás resultados extraños cuando las líneas se vean como expresiones regulares
The Archetypal Paul