Cómo encontrar las rutas comunes a partir de una lista de rutas/archivos

Cómo encontrar las rutas comunes a partir de una lista de rutas/archivos

Preludio:

Dada una entrada ordenada de una lista de rutas/archivos, ¿cómo encontrar sus rutas comunes?

Traducido al término tecnológico, si se alimenta la entrada ordenada desde la entrada estándar, ¿cómo elegir el prefijo adecuado más corto de la entrada estándar?

Aquí el "prefijo" tiene el significado normal, por ejemplo, la cadena 'abcde' tiene el prefijo 'abc'. Aquí está mi entrada de muestra.

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2'
/home/dave
/home/dave/file1
/home/dave/sub2/file2

Este es un ejemplo paraeliminar el prefijo adecuado sucesivodesde la entrada estándar, usando el comando sed:

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2' | sed "N; /^\(.*\)\n\1\//D; P; D" 
/home/dave/file1
/home/dave/sub2/file2

Pregunta:

Mi pregunta es comoconservar el prefijo adecuadoen su lugar, y elimine todas las líneas que tengan ese prefijo. Si ambos /home/dave/file1y /home/dave/sub2/file2tienen el prefijo /home/dave, se /home/daveconservarán mientras que los otros dos no. Es decir, hará todo lo contrario de lo que sedhace el comando anterior.

Más información:

  • La entrada ya estaría ordenada.
  • Si tengo /home/dave /home/dave/file1 /home/phil /home/phil/file2( echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2\n/home/phil\n/home/phil/file2'), esperaría /home/daveque y /home/philfuera la respuesta.

Solicitud:

Tengo dos volúmenes de disco que contienen contenido similar. Quiero copiar lo que hay en v1 pero que falta en v2 en otro volumen de disco, v3. Usando find, sorty comm, puedo obtener una lista de qué copiar, pero necesito limpiar más esa lista. Es decir, mientras tenga /home/daveen la lista, no necesito los otros dos.

¡Gracias!

Respuesta1

Esta respuesta usa Python. Como el OP quería eliminar los directorios cubiertos por sus padres, como yo había visto como una posibilidad, comencé a escribir un programa diferente para eliminar las cubiertas:

Ejemplo:

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2\n/home/phil\n/home/phil/file1' | removecoverings 
/home/phil
/home/dave

Código del removecoveringscomando:

#!/usr/bin/env python2

import sys

def list_startswith(a, b):
    if not len(a) >= len(b):
        return False
    return all(x == y for x,y in zip(a[:len(b)],b))

def removecoverings(it):
    g = list(it)
    g.sort(key=lambda v: len(v.split('/')), reverse=True)
    o = []
    while g:
        c = g.pop()
        d = []
        for v in g:
            if list_startswith(v.split('/'), c.split('/')):
                d.append(v)
        for v in d:
            g.remove(v)
        o.append(c)
    return o

for o in removecoverings(l.strip() for l in sys.stdin.readlines()):
    print o

Esta respuesta usa Python. También crea un prefijo común para componentes en lugar de cadenas. Es mejor para rutas como prefijo común de /ex/ampley no /exa/mpledebería serlo . Esto supone que lo que se busca es el mayor prefijo común y no una lista de prefijos sin sus cubiertas. Si tienes y esperas en lugar de . Esta no es la respuesta que estarías buscando.//ex/home/dave /home/dave/file1 /home/phil /home/phil/file2/home/dave /home/phil/home

Ejemplo:

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2' | commonprefix 
/home/dave

Código del commonprefixcomando:

#!/usr/bin/env python2

import sys

def commonprefix(l):
    # this unlike the os.path.commonprefix version
    # always returns path prefixes as it compares
    # path component wise
    cp = []
    ls = [p.split('/') for p in l]
    ml = min( len(p) for p in ls )

    for i in range(ml):

        s = set( p[i] for p in ls )         
        if len(s) != 1:
            break

        cp.append(s.pop())

    return '/'.join(cp)

print commonprefix(l.strip() for l in sys.stdin.readlines())

Respuesta2

Dado que la entrada está ordenada, el pseudocódigo sería:

$seen = last_line;
if current_line begins exactly as $seen then next
else { output current_line; $seen = current_line }

Traduciendo al código Perl (Sí, Perl, el lenguaje de escritura más bello de todos):

perl -e '
my $l = "\n";
while (<>) {
    if ($_ !~ /^\Q$l/) {
        print;
        chomp;
        $l = $_;
    }
}
'

Crédito:Ben Bacarisse @bsb.me.uk, de comp.lang.perl.misc. Gracias Ben, ¡funciona muy bien!

Respuesta3

Y la versión única de la respuesta de xpt. Nuevamente, suponiendo una entrada ordenada:

perl -lne 'BEGIN { $l="\n"; }; if ($_ !~ /^\Q$l/) { print $_; $l = $_; }'

Ejecutar en la entrada de ejemplo

/home/dave
/home/dave/file1
/home/dave/sub2/file2
/home/phil
/home/phil/file2 

usando

echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2\n/home/phil\n/home/phil/file2' | perl -lne 'BEGIN { $l="\n"; }; if ($_ !~ /^\Q$l/) { print $_; $l = $_; }'

da

/home/dave
/home/phil

La magia está en los argumentos de la línea de comandos de Perl: -enos permite proporcionar un script en la línea de comandos, -nitera sobre las líneas del archivo (colocando cada línea en $_) y -lse ocupa de las nuevas líneas por nosotros.

El script funciona usando lpara rastrear el último prefijo visto. El BEGINbloque se ejecuta antes de leer la primera línea e inicializa la variable en una cadena que no se verá (sin nuevas líneas). El condicional se ejecuta en cada línea del archivo (retenido por $_). El condicional se ejecuta en todas las líneas del archivo y dice "si la línea no tiene el valor actual de lcomo prefijo, imprima la línea y guárdela con el valor de l". Debido a los argumentos de la línea de comandos, este es esencialmente idéntico al otro script.

El problema es que ambos scripts asumen que el prefijo común existe como su propia línea, así que no busque el prefijo común para entradas como

/home/dave/file1
/home/dave/file2

información relacionada