Utilice la salida del encabezado para copiar archivos con espacios

Utilice la salida del encabezado para copiar archivos con espacios

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 directorypara 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 -rla 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 printfy el %qespecificador de formato). Luego, estas líneas se ordenan (numéricamente, en orden inverso) con sorty solo se conservan las 10 primeras.

Luego esto se alimenta al whilebucle. 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 %qy 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 ]] || continuejusto después de la for f in *; dolí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 lstiene los argumentos iy 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 findpodrá 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 lsformato 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

información relacionada