Cómo saber si un archivo de texto es un subconjunto de otro

12

Estoy tratando de encontrar una manera de determinar si un archivo de texto es un subconjunto de otro ...

Por ejemplo:

foo
bar

es un subconjunto de

foo
bar
pluto

Mientras:

foo
pluto

y

foo
bar

no son un subconjunto el uno del otro ...

¿Hay alguna manera de hacer esto con un comando?

Esta verificación debe ser una verificación cruzada y debe devolver:

file1 subset of file2 :    True
file2 subset of file1 :    True
otherwise             :    False
gc5
fuente
Solución potencialmente más eficiente (si los archivos también se ordenan): github.com/barrycarter/bcapps/blob/master/…
barrycarter

Respuestas:

11

Si se llama a esos contenidos de archivo file1, file2y file3en orden de aparición, puede hacerlo con el siguiente one-liner:

 # python -c "x=open('file1').read(); y=open('file2').read(); print x in y or y in x"
 True
 # python -c "x=open('file2').read(); y=open('file1').read(); print x in y or y in x"
 True
 # python -c "x=open('file1').read(); y=open('file3').read(); print x in y or y in x"
 False
Timo
fuente
Gracias por tu respuesta ... +1 ... No sé si aceptar mi respuesta porque la tuya no es específica de Unix-Linux y mi respuesta es un poco más rápida, en lo que respecta a la prueba ... ¿qué te parece?
gc5
De nada, hay, por supuesto, otras soluciones con más herramientas específicas de Unix. Pero esto parece un buen uso del inoperador de Python .
Timo
Hay un envoltorio de línea de comandos de Python para hacerlo más similar a Unix, con tuberías incorporadas, llamadas pyp: code.google.com/p/pyp . Creo que es trivial hacer que esta solución sea más unix como una herramienta de línea.
IBr
3

Con perl:

if perl -0777 -e '$n = <>; $h = <>; exit(index($h,$n)<0)' needle.txt haystack.txt
then echo needle.txt is found in haystack.txt
fi

-0octaldefine el delimitador de registro. Cuando ese número octal es mayor que 0377 (el valor de byte máximo), eso significa que no hay delimitador, es equivalente a hacerlo $/ = undef. En ese caso, <>devuelve el contenido completo de un solo archivo, ese es el modo slurp .

Una vez que tenemos el contenido de los archivos en dos $hy $nvariables, podemos usar index()para determinar si uno se encuentra en el otro.

Sin embargo, eso significa que todos los archivos se almacenan en la memoria, lo que significa que el método no funcionará para archivos muy grandes.

Para archivos mmappables (generalmente incluye archivos normales y la mayoría de los archivos buscables, como dispositivos de bloque), se puede solucionar usando mmap()los archivos, como con el Sys::Mmapmódulo perl:

if 
  perl -MSys::Mmap -le '
    open N, "<", $ARGV[0] || die "$ARGV[0]: $!";
    open H, "<", $ARGV[1] || die "$ARGV[1]: $!";
    mmap($n, 0, PROT_READ, MAP_SHARED, N);
    mmap($h, 0, PROT_READ, MAP_SHARED, H);
    exit (index($h, $n) < 0)' needle.txt haystack.txt
then
  echo needle.txt is found in haystack.txt
fi
Stéphane Chazelas
fuente
2

Encontré una solución gracias a esta pregunta

Básicamente estoy probando dos archivos a.txty b.txtcon este script:

#!/bin/bash

first_cmp=$(diff --unchanged-line-format= --old-line-format= --new-line-format='%L' "$1" "$2" | wc -l)
second_cmp=$(diff --unchanged-line-format= --old-line-format= --new-line-format='%L' "$2" "$1" | wc -l)

if [ "$first_cmp" -eq "0" -o "$second_cmp" -eq "0" ]
then
    echo "Subset"
    exit 0
else
    echo "Not subset"
    exit 1
fi

Si uno es subconjunto del otro el retorno de la escritura 0de Trueotra manera 1.

gc5
fuente
¿Qué hace% L? Este script no parece funcionar, y estoy tratando de depurarlo ...
Alex
De hecho, no recuerdo el significado de %Lhace tres años. De man diff(versión actual) %Lsignifica "contenido de la línea".
gc5
% L imprime el contenido de la línea "nueva". IOW, no imprima nada para líneas sin cambios o líneas antiguas, pero imprima el contenido de la línea para líneas nuevas.
PLG
¡Este script funciona para mí, fuera de la caja!
PLG
2

Si f1 es un subconjunto de f2, entonces f1 - f2 es un conjunto vacío. Partiendo de eso, podemos escribir una función is_subset y una función derivada de ella. Según la diferencia establecida entre 2 archivos de texto


sort_files () {
  f1_sorted = "$ 1.sorted"
  f2_sorted = "$ 2.sorted"

  Si [ ! -f $ f1_sorted]; luego
    gato $ 1 | ordenar | uniq> $ f1_sorted
  fi

  Si [ ! -f $ f2_sorted]; luego
    gato $ 2 | ordenar | uniq> $ f2_sorted
  fi
}

remove_sorted_files () {
  f1_sorted = "$ 1.sorted"
  f2_sorted = "$ 2.sorted"
  rm -f $ f1_sorted
  rm -f $ f2_sorted
}

set_union () {
  sort_files $ 1 $ 2
  cat "$ 1.sorted" "$ 2.sorted" | ordenar | uniq
  remove_sorted_files $ 1 $ 2
}

set_diff () {
  sort_files $ 1 $ 2
  cat "$ 1.sorted" "$ 2.sorted" "$ 2.sorted" | ordenar | uniq -u
  remove_sorted_files $ 1 $ 2
}

rset_diff () {
  sort_files $ 1 $ 2
  cat "$ 1.sorted" "$ 2.sorted" "$ 1.sorted" | ordenar | uniq -u
  remove_sorted_files $ 1 $ 2
}

is_subset () {
  sort_files $ 1 $ 2
  salida = $ (set_diff $ 1 $ 2)
  remove_sorted_files $ 1 $ 2

  si [-z $ salida]; luego
    volver 0
  más
    volver 1
  fi

}

Saurabh Hirani
fuente
¿Debería comenzar este script #!/bin/bash?
Alex
2

De http://www.catonmat.net/blog/set-operations-in-unix-shell/ :

Comm compara dos archivos ordenados línea por línea. Puede ejecutarse de tal manera que muestre líneas que solo aparecen en el primer archivo especificado. Si el primer archivo es un subconjunto del segundo, entonces todas las líneas en el primer archivo también aparecen en el segundo, por lo que no se produce ninguna salida:

$ comm -23 <(sort subset | uniq) <(sort set | uniq) | head -1
# comm returns no output if subset ⊆ set
# comm outputs something if subset ⊊ set
Alec
fuente