¿Cómo encuentro qué archivos faltan en una lista?

9

Tengo una lista de archivos que quiero verificar si existen en mi sistema de archivos. Pensé en hacer esto usando findcomo en:

for f in $(cat file_list); do
find . -name $f > /dev/null || print $f
done

(usando zsh) pero eso no funciona ya que findparece salir 0si encuentra o no el archivo. Supongo que podría pasarlo por alguna otra prueba, que prueba para ver si findproduce algo (crudo pero efectivo sería reemplazarlo > /dev/nullcon |grep ''), pero esto se siente como usar un troll para atrapar una cabra (otras nacionalidades podrían decir algo sobre mazos y nueces )

¿Hay alguna manera de findobligarme a darme un valor de salida útil? ¿O al menos para obtener una lista de esos archivos que no se encontraron? (Me imagino que esto último es quizás más fácil por alguna astuta elección de conexiones lógicas, pero parece que siempre me atoro cuando trato de resolverlo).

Antecedentes / Motivación: Tengo una copia de seguridad "maestra" y quiero verificar que existan algunos archivos en mi máquina local en mi copia de seguridad maestra antes de eliminarlos (para crear un poco de espacio). Así que hice una lista de los archivos, los sshedité en la máquina maestra, y luego no pude encontrar la mejor manera de encontrar los archivos faltantes.

Andrew Stacey
fuente
Actualicé mi solución para usar mucho más rápido locate.
usuario desconocido
@userunknown locateno muestra el estado actual del sistema de archivos, podría ser un día o incluso una semana de antigüedad. Eso es adecuado como base para probar las copias de seguridad.
Volker Siegel

Respuestas:

5

findconsidera no encontrar nada un caso especial de éxito (no se produjo ningún error). Una forma general de probar si los archivos coinciden con algunos findcriterios es probar si la salida de findestá vacía. Para una mejor eficiencia cuando hay archivos coincidentes, use -quiten GNU find para que se cierre en la primera coincidencia, o head( head -c 1si está disponible, de lo contrario, lo head -n 1que es estándar) en otros sistemas para que muera por una tubería rota en lugar de producir una salida larga.

while IFS= read -r name; do
  [ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list

En bash ≥4 o zsh, no necesita el findcomando externo para una simple coincidencia de nombre: puede usar **/$name. Versión bash:

shopt -s nullglob
while IFS= read -r name; do
  set -- **/"$name"
  [ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list

Versión de Zsh con un principio similar:

while IFS= read -r name; do
  set -- **/"$name"(N)
  [ $# -ge 1 ] || print -- "$name"
done <file_list

O aquí hay una forma más corta pero más críptica de probar la existencia de un archivo que coincida con un patrón. El calificador global Nhace que la salida esté vacía si no hay coincidencia, [1]conserva solo la primera coincidencia y e:REPLY=true:cambia cada coincidencia para expandirla en 1lugar del nombre del archivo coincidente. Entonces se **/"$name"(Ne:REPLY=true:[1]) falseexpande a true falsesi hay una coincidencia, o simplemente a falsesi no hay coincidencia.

while IFS= read -r name; do
  **/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list

Sería más eficiente combinar todos sus nombres en una sola búsqueda. Si el número de patrones no es demasiado grande para el límite de longitud de su sistema en una línea de comando, puede unir todos los nombres -o, hacer una sola findllamada y procesar posteriormente la salida. Si ninguno de los nombres contiene metacaracteres de shell (de modo que los nombres también son findpatrones), esta es una forma de postprocesar con awk (no probado):

set -o noglob; IFS='
'
set -- $(<file_list sed -e '2,$s/^/-o\
/')
set +o noglob; unset IFS
find . \( "$@" \) -print | awk -F/ '
    BEGIN {while (getline <"file_list") {found[$0]=0}}
    wanted[$0]==0 {found[$0]=1}
    END {for (f in found) {if (found[f]==0) {print f}}}
'

Otro enfoque sería usar Perl y File::Find, lo que facilita la ejecución del código Perl para todos los archivos en un directorio.

perl -MFile::Find -l -e '
    %missing = map {chomp; $_, 1} <STDIN>;
    find(sub {delete $missing{$_}}, ".");
    print foreach sort keys %missing'

Un enfoque alternativo es generar una lista de nombres de archivos en ambos lados y trabajar en una comparación de texto. Versión Zsh:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
Gilles 'SO- deja de ser malvado'
fuente
Estoy aceptando este por dos razones. Me gusta la zshsolución con la **sintaxis. Es una solución muy simple y, si bien puede que no sea la más eficiente en términos de máquina , ¡probablemente sea la más eficiente en términos de que realmente lo recuerde! Además, la primera solución aquí responde a la pregunta real en que se tuerce finden algo donde el código de salida distingue "Tengo una coincidencia" de "No obtuve una coincidencia".
Andrew Stacey
9

Puede usar statpara determinar si existe un archivo en el sistema de archivos.

Debe usar las funciones de shell incorporadas para probar si existen archivos.

while read f; do
   test -f "$f" || echo $f
done < file_list

La "prueba" es opcional y el script realmente funcionará sin él, pero lo dejé allí para facilitar la lectura.

Editar: si realmente no tiene más opción que trabajar para una lista de nombres de archivo sin rutas, le sugiero que cree una lista de archivos una vez con find, luego repítala con grep para descubrir qué archivos hay.

find -type f /dst > $TMPFILE
while read f; do
    grep -q "/$f$" $TIMPFILE || echo $f
done < file_list

Tenga en cuenta que:

  • la lista de archivos solo incluye archivos, no directorios,
  • la barra oblicua en el patrón de coincidencia grep es por lo que comparamos nombres de archivos completos, no parciales,
  • y el último '$' en el patrón de búsqueda es hacer coincidir el final de la línea para que no obtenga coincidencias de directorio, solo parches de nombre de archivo completo.
Caleb
fuente
stat necesita la ubicación exacta, ¿no es así? Estoy usando find porque solo tengo una lista de nombres de archivos y podrían estar en numerosos directorios. Lo siento si eso no estaba claro.
Andrew Stacey
Hmmm ¡Ya no dijiste que tenías nombres de archivo sin rutas! ¿Quizás puedas solucionar ESE problema en su lugar? Sería mucho más eficiente que ejecutar encontrar un montón de veces en el mismo conjunto de datos.
Caleb
Gracias por la edición, y lo siento nuevamente por no ser específico. El nombre / la ruta del archivo no es algo que vaya a arreglar; los archivos pueden estar en diferentes lugares en los dos sistemas, por lo que quiero una solución lo suficientemente robusta como para solucionarlo. ¡La computadora debería funcionar según mis especificaciones, no al revés! En serio, esto no es algo que hago a menudo: estaba buscando algunos archivos antiguos para eliminar para hacer espacio y solo quería una forma "rápida y sucia" para asegurarme de que estaban en mis copias de seguridad.
Andrew Stacey
En primer lugar, no necesitaría una ruta completa, solo una ruta relativa a la estructura de directorio que estaba respaldando. Permítame sugerirle que si la ruta no es la misma, hay una buena posibilidad de que el archivo no sea el mismo y que pueda obtener falsos positivos de su prueba. Parece que su solución puede ser más sucia que rápida; No quisiera verte quemado pensando que tienes algo que no tienes. Además, si los archivos son lo suficientemente valiosos para hacer una copia de seguridad en primer lugar, no debe eliminar las primarias, de lo contrario, debe hacer una copia de seguridad de sus copias de seguridad.
Caleb
¡Alaska! Dejé un montón de detalles para tratar de enfocar la pregunta y los estás completando con un montón de suposiciones que, debo decir, ¡son perfectamente razonables pero están completamente equivocadas! Baste decir que que si el archivo está allí y está en un directorio con un tipo particular de nombre, sé que es el archivo original y es seguro eliminar la copia en mi máquina.
Andrew Stacey
1

Un primer enfoque simplista podría ser:

a) ordenar su lista de archivos:

sort file.lst > sorted.lst 
for f in $(< sortd.lst) ; do find -name $f -printf "%f\n"; done > found.lst
diff sorted.lst found.lst

para encontrar faltas, o

comm sorted.lst found.lst

para encontrar coincidencias

  • Trampas:
    • Las nuevas líneas en los nombres de archivo son muy difíciles de manejar
    • los espacios en blanco y cosas similares en los nombres de archivo tampoco son agradables. Pero dado que tiene control sobre los archivos en la lista de archivos, tal vez esta solución ya sea suficiente, sin embargo ...
  • Inconvenientes:

    • Cuando find encuentra un archivo, sigue ejecutándose para encontrar otro y otro. Sería bueno omitir más búsquedas.
    • find podría buscar varios archivos a la vez, con cierta preparación:

      find -name a.file -or -name -b.file -or -name c.file ...

¿Podría ser una opción? Una vez más, se supone una lista clasificada de archivos:

 for f in $(< sorted.tmp) ; do locate --regexp "/"$f"$" > /dev/null || echo missing $f ; done

Una búsqueda de foo.bar no coincidirá con un archivo foo.ba u oo.bar con --regexp-construct (no debe ser confundido por regex sin p).

Puede especificar una base de datos específica para localizar, y debe actualizarla antes de buscar, si necesita los resultados más recientes.

usuario desconocido
fuente
1

Creo que esto también puede ser útil.

Esta es una solución de una línea, en caso de que opte por que su "lista" sea archivos reales que desea sincronizar con otra carpeta:

function FUNCsync() { local fileCheck="$synchronizeTo/$1"; if [[ ! -f "$fileCheck" ]];then echo "$fileCheck";fi; };export -f FUNCsync;find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

para ayudar a leer:

function FUNCsync() {
  local fileCheck="$synchronizeTo/$1";
  if [[ ! -f "$fileCheck" ]];then 
    echo "$fileCheck";
  fi; 
};export -f FUNCsync;
find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

este ejemplo excluye los archivos de respaldo "* ~" y limita el tipo de archivo normal "-type f"

Poder de acuario
fuente
0
FIND_EXP=". -type f \( "
while read f; do
   FIND_EXP="${FIND_EXP} -iname $f -or"
done < file_list
FIND_EXP="${var%-or}"
FIND_EXP="${FIND_EXP} \)"
find ${FIND_EXP}

¿Tal vez?

Hola71
fuente
0

¿Por qué no simplemente comparar la longitud de la lista de consultas con la longitud de la lista de resultados?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Holger Brandl
fuente