Ich habe 11 Dateien mit Leerzeichen im Namen in einem Ordner und möchte die neuesten 10 kopieren. Normalerweise ls -t | head -n 10
bekomme 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 directory
fü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 -r
der 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 printf
und den %q
Formatbezeichner). Diese Zeilen werden dann mit sortiert (numerisch, in umgekehrter Reihenfolge) sort
, und nur die ersten 10 werden behalten.
Dies wird dann in die while
Schleife 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 %q
und 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 ]] || continue
direkt nach der for f in *; do
Zeile 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 ls
hat , können Sie etwas in dieser Art verwenden:i
q
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 find
es 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 ls
Ausgabeformat 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