
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/file1
y /home/dave/sub2/file2
tienen el prefijo /home/dave
, se /home/dave
conservarán mientras que los otros dos no. Es decir, hará todo lo contrario de lo que sed
hace 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/dave
que y/home/phil
fuera 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
, sort
y comm
, puedo obtener una lista de qué copiar, pero necesito limpiar más esa lista. Es decir, mientras tenga /home/dave
en 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 removecoverings
comando:
#!/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/ample
y no /exa/mple
deberí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 commonprefix
comando:
#!/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: -e
nos permite proporcionar un script en la línea de comandos, -n
itera sobre las líneas del archivo (colocando cada línea en $_
) y -l
se ocupa de las nuevas líneas por nosotros.
El script funciona usando l
para rastrear el último prefijo visto. El BEGIN
bloque 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 l
como 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