Verwenden Sie die Ausgabe von head, um Dateien mit Leerzeichen zu kopieren

Verwenden Sie die Ausgabe von head, um Dateien mit Leerzeichen zu kopieren

Ich habe 11 Dateien mit Leerzeichen im Namen in einem Ordner und möchte die neuesten 10 kopieren. Normalerweise ls -t | head -n 10bekomme ich nur die neuesten 10 Dateien. Wenn ich den Ausdruck in einer cp-Anweisung verwenden möchte, erhalte ich eine Fehlermeldung, dass die Dateien aufgrund des Leerzeichens im Namen nicht gefunden werden konnten.

Beispiel: cp: cannot stat ‘10’: No such file or directoryfür eine Datei namens10 abc.pdf

Die cp-Anweisung:cp $(ls -t | head -n 10) ~/...

Wie bekomme ich das zum Laufen?

Antwort1

Wenn Sie Bash verwenden und eine 100 % sichere Methode wollen (was Sie, wie ich annehme, wollen, nachdem Sie auf die harte Tour gelernt haben, dass Sie mit Dateinamen sehr ernst umgehen müssen), dann ist hier die Lösung:

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
)

Schauen wir uns die einzelnen Teile einmal an:

for f in *; do
    printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done

Dies druckt auf stdout die Bedingungen des Formulars

Timestamp Filename

wobei der Zeitstempel das Änderungsdatum (erhalten mit -rder Option von date) in Sekunden seit Epoche ist und der Dateiname eine zitierte Version des Dateinamens ist.die als Shell-Eingabe wiederverwendet werden können(siehe help printfund den %qFormatbezeichner). Diese Zeilen werden dann mit sortiert (numerisch, in umgekehrter Reihenfolge) sort, und nur die ersten 10 werden behalten.

Dies wird dann in die whileSchleife eingespeist. Die Zeitstempel werden mit der Zuweisung entfernt file=${file# *}(dadurch wird alles bis einschließlich des ersten Leerzeichens entfernt), dann eval "file=$file"werden in der anscheinend gefährlichen Zeile die durch eingeführten Escape-Zeichen entfernt printf %qund schließlich können wir die Datei sicher kopieren.

Wahrscheinlich nicht der beste Ansatz oder die beste Implementierung, aber 100 % sicher in Bezug auf alle möglichen Dateinamen und erledigt die Aufgabe. Allerdings werden normale Dateien, Verzeichnisse usw. gleich behandelt. Wenn Sie es auf normale Dateien beschränken möchten, fügen Sie [[ -f $f ]] || continuedirekt nach der for f in *; doZeile hinzu. Außerdem werden versteckte Dateien nicht berücksichtigt. Wenn Sie versteckte Dateien möchten (aber natürlich nicht .noch ), fügen Sie hinzu ...shopt -s dotglob


Eine weitere 100%ige Bash-Lösung besteht darin, Bash direkt zum Sortieren der Dateien zu verwenden. Hier ist ein Ansatz mit Quicksort:

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[@]}" )
}

Sortieren Sie sie dann aus, behalten Sie die ersten 10 und kopieren Sie sie an Ihr Ziel:

$ shopt -s nullglob
$ quicksort *
$ cp -- "${quicksort_ret[@]:0:10}" Destination

Wie bei der vorherigen Methode werden hiermit normale Dateien, Verzeichnisse usw. gleich behandelt und versteckte Dateien übersprungen.


Ein anderer Ansatz: Wenn Ihr die Argumente und lshat , können Sie etwas in dieser Art verwenden:iq

ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit

Dadurch wird der Inode der Datei angezeigt und findes können Befehle für Dateien ausgeführt werden, auf die durch ihren Inode verwiesen wird.

Dasselbe, dies behandelt auch Verzeichnisse usw., nicht nur normale Dateien. Ich mag das nicht wirklich, da es zu sehr vom lsAusgabeformat abhängt …

Antwort2

Bei jeder Dateigruppe in einem Verzeichnis werden dabei immer die ältesten Dateien ausgelassen:

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 ist vollständig portierbar und druckt ein sicher in Shell-Anführungszeichen gesetztes Array von Dateinamen, unabhängig davon, welche Zeichen sie zurückgeben. In Ihrem Fall müssen Sie dro pone nur einmal ausführen, wie folgt:

{   printf 'cp -t ~"/..."'
    less_oldest "${dir_of_11_files}/"*
} | sh

verwandte Informationen