У меня в папке 11 файлов с пробелами в имени, и я хочу скопировать самые новые 10. Раньше я ls -t | head -n 10
получал только самые новые 10 файлов. Когда я хочу использовать выражение в операторе cp, я получаю ошибку, что файлы не могут быть найдены из-за пробела в имени.
Например: cp: cannot stat ‘10’: No such file or directory
для файла с именем10 abc.pdf
Заявление CP:cp $(ls -t | head -n 10) ~/...
Как мне это сделать?
решение1
Если вы используете Bash и вам нужен 100% безопасный метод (который, я полагаю, вам нужен, теперь, когда вы на собственном горьком опыте узнали, что с именами файлов нужно обращаться серьезно), вот он:
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
)
Давайте рассмотрим его несколько частей:
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done
Это выведет на стандартный вывод условия в форме
Timestamp Filename
где timestamp — это дата изменения (полученная с помощью -r
параметра date
) в секундах с момента начала эпохи, а Filename — это заключенная в кавычки версия имени файла.которые можно повторно использовать в качестве входных данных оболочки(см. help printf
и %q
спецификатор формата). Затем эти строки сортируются (по номерам, в обратном порядке) с помощью sort
, и сохраняются только первые 10.
Затем это подается в while
цикл. Временные метки удаляются с помощью file=${file# *}
назначения (это избавляет от всего до первого пробела включительно), затем явно опасная строка eval "file=$file"
избавляется от экранирующих символов, введенных printf %q
, и, наконец, мы можем безопасно скопировать файл.
Возможно, это не лучший подход или реализация, но 100% гарантированная безопасность относительно любых возможных имен файлов, и она выполняет свою работу. Хотя это будет обрабатывать обычные файлы, каталоги и т. д. все равно. Если вы хотите ограничиться обычными файлами, добавьте [[ -f $f ]] || continue
сразу после for f in *; do
строки. Кроме того, он не будет учитывать скрытые файлы. Если вам нужны скрытые файлы (но не .
, ..
конечно), добавьте shopt -s dotglob
.
Другое 100% Bash-решение — использовать Bash напрямую для сортировки файлов. Вот подход с использованием быстрой сортировки:
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[@]}" )
}
Затем отсортируйте их, сохраните первые 10 и скопируйте их в место назначения:
$ shopt -s nullglob
$ quicksort *
$ cp -- "${quicksort_ret[@]:0:10}" Destination
Как и в предыдущем методе, этот метод обрабатывает обычные файлы, каталоги и т. д. одинаково и пропускает скрытые файлы.
Другой подход: если у вас ls
есть аргументы i
и q
, вы можете использовать что-то вроде этого:
ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit
Это покажет индекс файла и find
позволит выполнять команды над файлами, на которые ссылается их индекс.
То же самое, это также будет обрабатывать каталоги и т. д., а не только обычные файлы. Мне это не очень нравится, так как оно слишком полагается на ls
выходной формат…
решение2
Для любой группы файлов в каталоге это всегда приведет к исключению самого старого файла:
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"
}
Он полностью переносим и печатает массив имен файлов, заключенных в безопасные кавычки оболочки, независимо от того, какие символы они могут вернуть. В вашем случае, поскольку вам нужно только dro pone, вам нужно запустить его только один раз, например:
{ printf 'cp -t ~"/..."'
less_oldest "${dir_of_11_files}/"*
} | sh