Используйте вывод из head для копирования файлов с пробелами

Используйте вывод из head для копирования файлов с пробелами

У меня в папке 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

Связанный контент