¿Cómo obtener los últimos N archivos en un directorio?
15
Tengo muchos archivos que están ordenados por nombre de archivo en un directorio. Deseo copiar los archivos finales N (por ejemplo, N = 4) a mi directorio de inicio. ¿Cómo debería hacerlo?
En todas las respuestas a continuación, es posible que deba agregar un "LC_ALL = ..." delante de los comandos que usan los rangos, de modo que sus rangos usen la configuración regional adecuada para usted (las configuraciones regionales pueden cambiar y el orden de los caracteres puede cambiar) mucho. Ej., en algunas localidades, [az] puede contener todas las letras del alfabeto excepto "Z", ya que las letras están ordenadas: aAbBcC .... zZ. Existen otras variaciones (letras acentuadas e incluso ordenamientos más exóticos). Una opción habitual es:LC_ALL=C
Olivier Dulac
Respuestas:
23
Esto se puede hacer fácilmente con las matrices bash / ksh93 / zsh:
a=(*)
cp --"${a[@]: -4}"~/
Esto funciona para todos los nombres de archivos no ocultos, incluso si contienen espacios, pestañas, líneas nuevas u otros caracteres difíciles (suponiendo que haya al menos 4 archivos no ocultos en el directorio actual con bash).
Cómo funciona
a=(*)
Esto crea una matriz acon todos los nombres de archivo. Los nombres de archivo devueltos por bash están ordenados alfabéticamente. (Supongo que esto es lo que quiere decir con "ordenado por nombre de archivo" ).
${a[@]: -4}
Esto devuelve los últimos cuatro elementos de la matriz a(siempre que la matriz contenga al menos 4 elementos con bash).
cp -- "${a[@]: -4}" ~/
Esto copia los últimos cuatro nombres de archivo a su directorio de inicio.
Para copiar y renombrar al mismo tiempo
Esto copiará los últimos cuatro archivos solo al directorio de inicio y, al mismo tiempo, antepondrá la cadena a_al nombre del archivo copiado:
Copie desde un directorio diferente y también cambie el nombre
Si usamos en a=(./some_dir/*)lugar de a=(*), entonces tenemos el problema de que el directorio se adjunta al nombre del archivo. Una solución es:
a=(./some_dir/*)for f in"${a[@]: -4}";do cp "$f"~/a_"${f##*/}";done
Otra solución es usar una subshell y cdel directorio en la subshell:
(cd ./some_dir && a=(*)&&for f in"${a[@]: -4}";do cp --"$f"~/a_"$f";done)
Cuando se completa el subshell, el shell nos devuelve al directorio original.
Asegurarse de que el pedido sea consistente
La pregunta pide archivos "ordenados por nombre de archivo". Ese orden, señala Olivier Dulac en los comentarios, variará de un lugar a otro. Si es importante tener resultados fijos independientes de la configuración de la máquina, lo mejor es especificar la configuración regional explícitamente cuando ase define la matriz . Por ejemplo:
LC_ALL=C a=(*)
Puede averiguar en qué localidad se encuentra actualmente ejecutando el localecomando.
Si está utilizando zsh, puede incluir entre paréntesis ()una lista de los llamados calificadores globales que seleccionan los archivos deseados. En su caso, eso sería
cp *(On[1,4])~/
Aquí Onordena los nombres de los archivos alfabéticamente en orden inverso y [1,4]solo toma los primeros 4 de ellos.
Puede hacer esto más robusto seleccionando solo archivos simples (excluyendo directorios, tuberías, etc.) con ., y también agregando --un cpcomando para tratar los archivos cuyos nombres comienzan -correctamente, por lo que:
cp --*(.On[1,4])~
Agregue el Dcalificador si también desea considerar archivos ocultos (archivos de puntos):
@ StéphaneChazelas sí, dejemos en claro para los futuros lectores que ordenar alfabéticamente en la dirección normal ( on) es el comportamiento predeterminado, por lo que no es necesario especificar esto, y -frente a los números se cuentan los nombres de los archivos desde la parte inferior.
jimmij
7
Aquí hay una solución que usa comandos bash extremadamente simples.
Esto es muy similar a la bashsolución de matriz específica ofrecida, pero debería ser portátil a cualquier shell compatible con POSIX. Algunas notas sobre ambos métodos:
Es importante que usted dirige sus cpargumentos w / cp --o se obtiene uno de entre .un punto o /en la cabecera de cada nombre. Si no lo hace, se arriesga a un -guión principal en cpel primer argumento que puede interpretarse como una opción y provocar que la operación falle o que, de lo contrario, arroje resultados no deseados.
Incluso si trabaja con el directorio actual como directorio de origen, esto se hace fácilmente como ... set -- ./*o array=(./*).
Es importante cuando se usan ambos métodos para garantizar que tenga al menos tantos elementos en su matriz arg como intente eliminar; lo hago aquí con una expansión matemática. Lo shiftúnico ocurre si hay al menos 4 elementos en la matriz arg, y luego solo aleja esos primeros argumentos que generan un excedente de 4.
Entonces, en un set 1 2 3 4 5caso, cambiará la 1distancia, pero en un set 1 2 3caso, no cambiará nada.
Por ejemplo: a=(1 2 3); echo "${a[@]: -4}"imprime una línea en blanco.
Si está copiando de un directorio a otro, puede usar pax:
Si solo hay archivos y sus nombres no contienen espacios en blanco o nueva línea (y no ha modificado $IFS) o caracteres globales (o algunos caracteres no imprimibles con algunas implementaciones de ls), y no comience con ., entonces puede hacer esto :
¡Gracias! Funciona. ¿Hay alguna posibilidad de anteponer rápidamente un prefijo a_a los cuatro archivos? Lo intenté echo "a_$(ls | tail -n 4)", pero solo antepone el primer archivo.
Sibbs Gambling
@SibbsGambling Mi enfoque no es adecuado para eso, pero la respuesta de John1024 se puede adaptar fácilmente a eso.
Hauke Laging
55
En general, la lssalida de análisis se considera una forma incorrecta porque generalmente falla horriblemente en los nombres de archivo que contienen no solo espacios, sino también pestañas, líneas nuevas u otros caracteres válidos pero "difíciles".
HBruijn
2
@HaukeLaging Incluso si actualmente no es un problema (porque no tengo nombres de fuego "complicados" en mi directorio), podría tenerlo en 2 años, y de repente las cosas se me caen ...
glglgl
1
Esta es una solución bash simple sin ningún código de bucle. Copie los últimos 4 archivos del directorio actual al destino:
¿Cómo se asegura de que findle da los archivos en el orden deseado? ¿Cómo se asegura de que los archivos que contienen caracteres de espacio en blanco (incluidas las nuevas líneas) en sus nombres se manejan correctamente?
LC_ALL=C
Respuestas:
Esto se puede hacer fácilmente con las matrices bash / ksh93 / zsh:
Esto funciona para todos los nombres de archivos no ocultos, incluso si contienen espacios, pestañas, líneas nuevas u otros caracteres difíciles (suponiendo que haya al menos 4 archivos no ocultos en el directorio actual con
bash
).Cómo funciona
a=(*)
Esto crea una matriz
a
con todos los nombres de archivo. Los nombres de archivo devueltos por bash están ordenados alfabéticamente. (Supongo que esto es lo que quiere decir con "ordenado por nombre de archivo" ).${a[@]: -4}
Esto devuelve los últimos cuatro elementos de la matriz
a
(siempre que la matriz contenga al menos 4 elementos conbash
).cp -- "${a[@]: -4}" ~/
Esto copia los últimos cuatro nombres de archivo a su directorio de inicio.
Para copiar y renombrar al mismo tiempo
Esto copiará los últimos cuatro archivos solo al directorio de inicio y, al mismo tiempo, antepondrá la cadena
a_
al nombre del archivo copiado:Copie desde un directorio diferente y también cambie el nombre
Si usamos en
a=(./some_dir/*)
lugar dea=(*)
, entonces tenemos el problema de que el directorio se adjunta al nombre del archivo. Una solución es:Otra solución es usar una subshell y
cd
el directorio en la subshell:Cuando se completa el subshell, el shell nos devuelve al directorio original.
Asegurarse de que el pedido sea consistente
La pregunta pide archivos "ordenados por nombre de archivo". Ese orden, señala Olivier Dulac en los comentarios, variará de un lugar a otro. Si es importante tener resultados fijos independientes de la configuración de la máquina, lo mejor es especificar la configuración regional explícitamente cuando
a
se define la matriz . Por ejemplo:Puede averiguar en qué localidad se encuentra actualmente ejecutando el
locale
comando.fuente
Si está utilizando
zsh
, puede incluir entre paréntesis()
una lista de los llamados calificadores globales que seleccionan los archivos deseados. En su caso, eso seríaAquí
On
ordena los nombres de los archivos alfabéticamente en orden inverso y[1,4]
solo toma los primeros 4 de ellos.Puede hacer esto más robusto seleccionando solo archivos simples (excluyendo directorios, tuberías, etc.) con
.
, y también agregando--
uncp
comando para tratar los archivos cuyos nombres comienzan-
correctamente, por lo que:Agregue el
D
calificador si también desea considerar archivos ocultos (archivos de puntos):fuente
*([-4,-1])
.on
) es el comportamiento predeterminado, por lo que no es necesario especificar esto, y-
frente a los números se cuentan los nombres de los archivos desde la parte inferior.Aquí hay una solución que usa comandos bash extremadamente simples.
Explicación:
Encuentra todos los archivos en el directorio actual.
Se ordena alfabéticamente.
Mostrar solo las últimas 4 líneas.
Recorre cada línea que ejecuta el comando copiar.
fuente
Siempre que encuentre que el tipo de shell es agradable, puede hacer lo siguiente:
Esto es muy similar a la
bash
solución de matriz específica ofrecida, pero debería ser portátil a cualquier shell compatible con POSIX. Algunas notas sobre ambos métodos:Es importante que usted dirige sus
cp
argumentos w /cp --
o se obtiene uno de entre.
un punto o/
en la cabecera de cada nombre. Si no lo hace, se arriesga a un-
guión principal encp
el primer argumento que puede interpretarse como una opción y provocar que la operación falle o que, de lo contrario, arroje resultados no deseados.set -- ./*
oarray=(./*)
.Es importante cuando se usan ambos métodos para garantizar que tenga al menos tantos elementos en su matriz arg como intente eliminar; lo hago aquí con una expansión matemática. Lo
shift
único ocurre si hay al menos 4 elementos en la matriz arg, y luego solo aleja esos primeros argumentos que generan un excedente de 4.set 1 2 3 4 5
caso, cambiará la1
distancia, pero en unset 1 2 3
caso, no cambiará nada.a=(1 2 3); echo "${a[@]: -4}"
imprime una línea en blanco.Si está copiando de un directorio a otro, puede usar
pax
:... que aplicaría una
sed
sustitución de estilo a todos los nombres de archivos a medida que los copia.fuente
Si solo hay archivos y sus nombres no contienen espacios en blanco o nueva línea (y no ha modificado
$IFS
) o caracteres globales (o algunos caracteres no imprimibles con algunas implementaciones dels
), y no comience con.
, entonces puede hacer esto :fuente
a_
a los cuatro archivos? Lo intentéecho "a_$(ls | tail -n 4)"
, pero solo antepone el primer archivo.ls
salida de análisis se considera una forma incorrecta porque generalmente falla horriblemente en los nombres de archivo que contienen no solo espacios, sino también pestañas, líneas nuevas u otros caracteres válidos pero "difíciles".Esta es una solución bash simple sin ningún código de bucle. Copie los últimos 4 archivos del directorio actual al destino:
fuente
find
le da los archivos en el orden deseado? ¿Cómo se asegura de que los archivos que contienen caracteres de espacio en blanco (incluidas las nuevas líneas) en sus nombres se manejan correctamente?