Tengo 11 archivos con espacios en su nombre en una carpeta y quiero copiar los 10 más nuevos. Solía ls -t | head -n 10
obtener solo los 10 archivos más nuevos. Cuando quiero usar la expresión en una declaración cp, aparece un error que indica que no se pudieron encontrar los archivos debido al espacio en el nombre.
Por ejemplo: cp: cannot stat ‘10’: No such file or directory
para un archivo llamado10 abc.pdf
La declaración del CP:cp $(ls -t | head -n 10) ~/...
¿Cómo hago para que esto funcione?
Respuesta1
Si estás usando Bash y quieres un método 100% seguro (lo cual, supongo, quieres, ahora que has aprendido por las malas que debes manejar los nombres de archivos en serio), aquí tienes:
shopt -s nullglob
while IFS= read -r file; do
file=${file#* }
eval "file=$file"
cp "$file" Destination
done < <(
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done | sort -rn | head -n10
)
Echemos un vistazo a sus distintas partes:
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done
Esto se imprimirá en los términos estándar del formulario.
Timestamp Filename
donde marca de tiempo es la fecha de modificación (obtenida con -r
la opción de date
) en segundos ya que Época y Nombre de archivo es una versión citada del nombre de archivoque se puede reutilizar como entrada de shell(ver help printf
y el %q
especificador de formato). Luego, estas líneas se ordenan (numéricamente, en orden inverso) con sort
y solo se conservan las 10 primeras.
Luego esto se alimenta al while
bucle. Las marcas de tiempo se eliminan con la file=${file# *}
asignación (esto elimina todo hasta el primer espacio incluido), luego la línea aparentemente peligrosa eval "file=$file"
elimina los caracteres de escape introducidos por printf %q
y finalmente podemos copiar el archivo de forma segura.
Probablemente no sea el mejor enfoque o implementación, pero 100% garantiza la seguridad con respecto a cualquier posible nombre de archivo y hace el trabajo. Sin embargo, esto tratará los archivos, directorios, etc. normales de todos modos. Si desea restringir a archivos normales, agregue [[ -f $f ]] || continue
justo después de la for f in *; do
línea. Además, no considerará archivos ocultos. Si desea archivos ocultos (pero no .
ni ..
, por supuesto), agregue shopt -s dotglob
.
Otra solución 100% Bash es utilizar Bash directamente para ordenar los archivos. A continuación se muestra un método que utiliza una clasificación rápida:
quicksort() {
# quicksorts the filenames passed as positional parameters
# wrt modification time, newest first
# return array is quicksort_ret
if (($#==0)); then
quicksort_ret=()
return
fi
local pivot=$1 oldest=() newest=() f
shift
for f; do
if [[ $f -nt $pivot ]]; then
newest+=( "$f" )
else
oldest+=( "$f" )
fi
done
quicksort "${oldest[@]}"
oldest=( "${quicksort_ret[@]}" )
quicksort "${newest[@]}"
quicksort_ret+=( "$pivot" "${oldest[@]}" )
}
Luego, ordénalos, guarda los primeros 10 y cópialos a tu destino:
$ shopt -s nullglob
$ quicksort *
$ cp -- "${quicksort_ret[@]:0:10}" Destination
Igual que el método anterior, este tratará los archivos, directorios, etc. normales de todos modos y omitirá los archivos ocultos.
Para otro enfoque: si ls
tiene los argumentos i
y q
, puede usar algo como esto:
ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit
Esto mostrará el inodo del archivo y find
podrá ejecutar comandos en archivos a los que hace referencia su inodo.
Lo mismo, esto también tratará directorios, etc., no solo archivos normales. Realmente no me gusta este, ya que depende demasiado del ls
formato de salida...
Respuesta2
Para cualquier grupo de archivos en un directorio, esto siempre omitirá el más antiguo:
less_oldest() {
while [ -e "$1" ] ; do
for f do [ "$f" -ot "$1" ] && break
done && { set -- "$@" "$1"
shift ; continue
} ; shift ; break
done
printf '///%s///\n' "$@" |
sed "s|'"'|&"&"&|g;'"s|///|'|g"
}
Es totalmente portátil e imprime una serie de nombres de archivos entre comillas de forma segura, independientemente de los caracteres que puedan devolver. En su caso, debido a que solo necesita dejar caer, solo necesita ejecutarlo una vez como:
{ printf 'cp -t ~"/..."'
less_oldest "${dir_of_11_files}/"*
} | sh