Función bash para encontrar el nuevo patrón de coincidencia de archivos

141

En Bash, me gustaría crear una función que devuelva el nombre de archivo del archivo más nuevo que coincida con un patrón determinado. Por ejemplo, tengo un directorio de archivos como:

Directory/
   a1.1_5_1
   a1.2_1_4
   b2.1_0
   b2.2_3_4
   b2.3_2_0

Quiero el archivo más nuevo que comienza con 'b2'. ¿Cómo hago esto en bash? Necesito tener esto en mi ~/.bash_profileguión.

jlconlin
fuente
44
visite superuser.com/questions/294161/… para obtener más sugerencias de respuesta. La clasificación es el paso clave para obtener su archivo más reciente
Wolfgang Fahl el

Respuestas:

229

El lscomando tiene un parámetro -tpara ordenar por tiempo. Luego puedes tomar el primero (el más nuevo) con head -1.

ls -t b2* | head -1

Pero cuidado: ¿por qué no deberías analizar la salida de ls?

Mi opinión personal: el análisis lssolo es peligroso cuando los nombres de archivo pueden contener caracteres divertidos como espacios o líneas nuevas. Si puede garantizar que los nombres de archivo no contendrán caracteres divertidos, entonces el análisis lses bastante seguro.

Si está desarrollando un script que está destinado a ser ejecutado por muchas personas en muchos sistemas en muchas situaciones diferentes, le recomiendo no analizar ls.

Aquí se explica cómo hacerlo "correctamente": ¿Cómo puedo encontrar el archivo más reciente (más nuevo, más antiguo, más antiguo) en un directorio?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done
lesmana
fuente
8
Nota para los demás: si está haciendo esto para un directorio, agregaría la opción -d a ls, como esto 'ls -td <patrón> | cabeza -1 '
ken.ganong
55
El enlace de análisis LS dice que no debe hacer esto y recomienda los métodos en BashFAQ 99 . Estoy buscando un 1-liner en lugar de algo a prueba de balas para incluir en un script, por lo que continuaré analizando ls inseguramente como @lesmana.
Eponymous
1
@Eponymous: si está buscando un revestimiento sin usar el frágil ls, printf "%s\n" b2* | head -1lo hará por usted.
David Ongaro
2
@DavidOngaro La pregunta no dice que los nombres de los archivos son números de versión. Esto se trata de tiempos de modificación. Incluso con el supuesto de nombre de archivo b2.10_5_2mata esta solución.
Epónimo
1
Tu única línea me está dando la respuesta correcta, pero la forma "correcta" en realidad me está dando el archivo más antiguo . ¿Alguna idea de por qué?
NewNameStat
15

La combinación de findy lsfunciona bien para

  • nombres de archivo sin líneas nuevas
  • cantidad no muy grande de archivos
  • nombres de archivo no muy largos

La solución:

find . -name "my-pattern" -print0 |
    xargs -r -0 ls -1 -t |
    head -1

Vamos a desglosarlo:

Con findpodemos hacer coincidir todos los archivos interesantes como este:

find . -name "my-pattern" ...

luego usando -print0podemos pasar todos los nombres de archivos de forma segura a la lssiguiente:

find . -name "my-pattern" -print0 | xargs -r -0 ls -1 -t

findAquí se pueden agregar parámetros y patrones de búsqueda adicionales

find . -name "my-pattern" ... -print0 | xargs -r -0 ls -1 -t

ls -tclasificará los archivos por hora de modificación (la más nueva primero) e imprimirá uno en una línea. Puede usar -cpara ordenar por hora de creación. Nota : esto se romperá con los nombres de archivo que contienen nuevas líneas.

Finalmente head -1nos obtiene el primer archivo en la lista ordenada.

Nota: xargs use límites del sistema para el tamaño de la lista de argumentos. Si este tamaño excede, xargsllamará lsvarias veces. Esto interrumpirá la clasificación y probablemente también la salida final. correr

xargs  --show-limits

para verificar los límites de tu sistema.

Nota 2: utilícela find . -maxdepth 1 -name "my-pattern" -print0si no desea buscar archivos a través de subcarpetas.

Nota 3: Como lo señaló @starfry: el -rargumento xargses para evitar la llamada de ls -1 -t, si el archivo no coincide find. Gracias por la sugerencia.

Boris Brodski
fuente
2
Esto es mejor que las soluciones basadas en ls, ya que funciona para directorios con muchísimos archivos, donde ls se ahoga.
Marcin Zukowski el
find . -name "my-pattern" ... -print0me dafind: paths must precede expression: `...'
Jaakko
Oh! ...significa "más parámetros". Solo omítelo, si no lo necesitas.
Boris Brodski
2
Descubrí que esto puede devolver un archivo que no coincide con el patrón si no hay archivos que sí coincidan con el patrón. Ocurre porque find no pasa nada a xargs que luego invoca ls sin listas de archivos, lo que hace que funcione en todos los archivos. La solución es agregar -ra la línea de comando xargs que le dice a xargs que no ejecute su línea de comando si no recibe nada en su entrada estándar.
Starfry
@starfry gracias! Buena atrapada. Agregué -ra la respuesta.
Boris Brodski
7

Esta es una posible implementación de la función Bash requerida:

# Print the newest file, if any, matching the given pattern
# Example usage:
#   newest_matching_file 'b2*'
# WARNING: Files whose names begin with a dot will not be checked
function newest_matching_file
{
    # Use ${1-} instead of $1 in case 'nounset' is set
    local -r glob_pattern=${1-}

    if (( $# != 1 )) ; then
        echo 'usage: newest_matching_file GLOB_PATTERN' >&2
        return 1
    fi

    # To avoid printing garbage if no files match the pattern, set
    # 'nullglob' if necessary
    local -i need_to_unset_nullglob=0
    if [[ ":$BASHOPTS:" != *:nullglob:* ]] ; then
        shopt -s nullglob
        need_to_unset_nullglob=1
    fi

    newest_file=
    for file in $glob_pattern ; do
        [[ -z $newest_file || $file -nt $newest_file ]] \
            && newest_file=$file
    done

    # To avoid unexpected behaviour elsewhere, unset nullglob if it was
    # set by this function
    (( need_to_unset_nullglob )) && shopt -u nullglob

    # Use printf instead of echo in case the file name begins with '-'
    [[ -n $newest_file ]] && printf '%s\n' "$newest_file"

    return 0
}

Utiliza solo los componentes internos de Bash y debe manejar archivos cuyos nombres contengan nuevas líneas u otros caracteres inusuales.

pjh
fuente
1
Podrías usarlo nullglob_shopt=$(shopt -p nullglob)y luego $nullglobvolver a ponerlo nullglobcomo estaba antes.
gniourf_gniourf
La sugerencia de @gniourf_gniourf para usar $ (shopt -p nullglob) es buena. Por lo general, trato de evitar el uso de la sustitución de comandos ( $()o backticks) porque es lento, particularmente bajo Cygwin, incluso cuando el comando solo usa incorporados. Además, el contexto de subshell en el que se ejecutan los comandos a veces puede hacer que se comporten de manera inesperada. También trato de evitar almacenar comandos en variables (como nullglob_shopt) porque pueden ocurrir cosas muy malas si obtiene el valor incorrecto de la variable.
pjh
Aprecio la atención a los detalles que pueden conducir a un oscuro fracaso cuando se pasan por alto. ¡Gracias!
Ron Burk
¡Me encanta que hayas elegido una forma más única de resolver el problema! Es una certeza que en Unix / Linux hay más de una forma de 'skin the cat!'. Incluso si esto requiere más trabajo, tiene el beneficio de mostrarle conceptos a las personas. Tener un +1!
Pryftan
3

Los nombres de archivo inusuales (como un archivo que contenga el \ncarácter válido pueden causar estragos en este tipo de análisis). Aquí hay una manera de hacerlo en Perl:

perl -le '@sorted = map {$_->[0]} 
                    sort {$a->[1] <=> $b->[1]} 
                    map {[$_, -M $_]} 
                    @ARGV;
          print $sorted[0]
' b2*

Esa es una transformación Schwartziana usada allí.

Glenn Jackman
fuente
1
¡Que el schwartz te acompañe!
Nathan Monteleone
Esta respuesta puede funcionar, pero no confiaría en ella dada la mala documentación.
Wolfgang Fahl el
1

Puede usar statcon un archivo glob y decorate-sort-undecorate con el tiempo de archivo agregado en el frente:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-
perro
fuente
no "stat: no se puede leer la información del sistema de archivos para '% m% t% N': no ​​existe dicho archivo o directorio"
Ken Ingram
Creo que esto podría ser para la versión Mac / FreeBSD de stat, si recuerdo sus opciones correctamente. Para obtener resultados similares en otras plataformas, puede usarstat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash
1

Encantamiento de la función de magia oscura para aquellos que desean la find ... xargs ... head ...solución anterior, pero en forma de función fácil de usar para que no tenga que pensar:

#define the function
find_newest_file_matching_pattern_under_directory(){
    echo $(find $1 -name $2 -print0 | xargs -0 ls -1 -t | head -1)
}

#setup:
#mkdir /tmp/files_to_move
#cd /tmp/files_to_move
#touch file1.txt
#touch file2.txt

#invoke the function:
newest_file=$( find_newest_file_matching_pattern_under_directory /tmp/files_to_move/ bc* )
echo $newest_file

Huellas dactilares:

file2.txt

Cual es:

El nombre de archivo con la marca de tiempo modificada más antigua del archivo en el directorio dado que coincide con el patrón dado.

Eric Leschinski
fuente
1

Usa el comando find.

Suponiendo que está usando Bash 4.2+, use -printf '%T+ %p\n'para el valor de marca de tiempo del archivo.

find $DIR -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Ejemplo:

find ~/Downloads -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Para obtener una secuencia de comandos más útil, consulte la secuencia de comandos find-latest aquí: https://github.com/l3x/helpers

l3x
fuente
para trabajar con nombres de archivo que contienen espacios de cambio de corte -d '' -f2,3,4,5,6,7,8,9 ...
valodzka
0

Hay una forma mucho más eficiente de lograr esto. Considere el siguiente comando:

find . -cmin 1 -name "b2*"

Este comando encuentra el último archivo producido exactamente hace un minuto con la búsqueda con comodines en "b2 *". Si desea archivos de los últimos dos días, será mejor que use el siguiente comando:

find . -mtime 2 -name "b2*"

Los "." representa el directorio actual. Espero que esto ayude.

Naufal
fuente
9
En realidad, esto no encuentra el "patrón de coincidencia de archivos más nuevo" ... simplemente encuentra todos los patrones de coincidencia de archivos creados hace un minuto o modificados hace dos días.
GnP
Esta respuesta se basó en la pregunta planteada. Además, puede modificar el comando para ver el último archivo que apareció hace un día más o menos. Depende de lo que intentes hacer.
Naufal
"ajustar" no es la respuesta. es como publicar esto como una respuesta: "Simplemente modifique el comando de búsqueda y encuentre la respuesta dependiendo de lo que quiera hacer".
Kennet Celeste
No estoy seguro sobre el comentario innecesario. Si cree que mi respuesta no se confirma, proporcione la razón adecuada de por qué mi respuesta no tiene sentido con EJEMPLOS. Si no puede hacerlo, abstenerse de hacer más comentarios.
Naufal
1
Su solución requiere que sepa cuándo se creó el último archivo. Eso no estaba en la pregunta, así que no, su respuesta no se basa en la pregunta planteada.
Bloke Down The Pub