У меня есть много файлов, которые упорядочены по имени файла в каталоге. Я хочу скопировать последние N (скажем, N = 4) файлов в мой домашний каталог. Как я должен это делать?
cp ./<the final 4 files> ~/
У меня есть много файлов, которые упорядочены по имени файла в каталоге. Я хочу скопировать последние N (скажем, N = 4) файлов в мой домашний каталог. Как я должен это делать?
cp ./<the final 4 files> ~/
Ответы:
Это легко сделать с помощью массивов bash / ksh93 / zsh:
a=(*)
cp -- "${a[@]: -4}" ~/
Это работает для всех не скрытых имен файлов, даже если они содержат пробелы, символы табуляции, новые строки или другие сложные символы (при условии, что в текущем каталоге есть как минимум 4 не скрытых файла с bash
).
a=(*)
Это создает массив a
со всеми именами файлов. Имена файлов, возвращаемые bash, сортируются в алфавитном порядке. (Я предполагаю, что это то, что вы подразумеваете под «упорядоченным по имени файла». )
${a[@]: -4}
Это возвращает последние четыре элемента массива a
(при условии, что массив содержит как минимум 4 элемента с bash
).
cp -- "${a[@]: -4}" ~/
Это копирует последние четыре имени файла в ваш домашний каталог.
Это скопирует последние четыре файла только в домашний каталог и в то же время добавит строку a_
к имени скопированного файла:
a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
Если мы используем a=(./some_dir/*)
вместо a=(*)
, тогда у нас есть проблема присоединения каталога к имени файла. Одним из решений является:
a=(./some_dir/*)
for f in "${a[@]: -4}"; do cp "$f" ~/a_"${f##*/}"; done
Другое решение - использовать подоболочку и cd
каталог в подоболочке:
(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done)
После завершения подоболочки оболочка возвращает нас в исходный каталог.
Вопрос просит файлы "упорядоченные по имени файла". Этот порядок, отмечает Оливье Дюлак в комментариях, будет варьироваться от одного региона к другому. Если важно иметь фиксированные результаты, не зависящие от настроек компьютера, то лучше всего указывать локаль явно при определении массива a
. Например:
LC_ALL=C a=(*)
Вы можете узнать, в какой локали вы находитесь в данный момент, запустив locale
команду.
Если вы используете, zsh
вы можете заключить в скобки ()
список так называемых глобальных квалификаторов, которые выбирают нужные файлы. В вашем случае это было бы
cp *(On[1,4]) ~/
Здесь On
имена файлов сортируются в алфавитном порядке в обратном порядке и [1,4]
занимают только первые 4 из них.
Вы можете сделать это более надежным путем выбора только обычные файлы ( за исключением каталогов, трубы и т.д.) с .
, а также путем присоединения --
к cp
команде, чтобы к файлам лакомств, имена которых начинаются с -
должным образом, так:
cp -- *(.On[1,4]) ~
Добавьте D
классификатор, если вы также хотите рассмотреть скрытые файлы (dot-файлы):
cp -- *(D.On[1,4]) ~
*([-4,-1])
.
on
) является поведением по умолчанию, поэтому указывать это не нужно, и -
перед цифрами отсчитывает имена файлов снизу.
Вот решение с использованием чрезвычайно простых команд bash.
find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done
Объяснение:
find . -maxdepth 1 -type f
Находит все файлы в текущем каталоге.
sort
Сортирует по алфавиту
tail -n 4
Показывать только последние 4 строки.
while read -r file; do cp "$file" ~/; done
Зацикливается на каждой строке, выполняя команду копирования.
Пока вы находите, что сортировка оболочки приемлема, вы можете просто сделать:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
Это очень похоже на bash
предлагаемое решение для конкретного массива, но должно переноситься на любую POSIX-совместимую оболочку. Некоторые заметки об обоих методах:
Важно, чтобы вы приводили свои cp
аргументы w / cp --
или вы получали одну из .
точек или /
заголовок каждого имени. Если вам не удастся сделать это, вы рискуете первым аргументом первой -
черты в cp
начале, который может быть интерпретирован как опция и может привести к сбою операции или к непредвиденным результатам.
set -- ./*
или array=(./*)
.При использовании обоих методов важно, чтобы в вашем массиве arg было как минимум столько элементов, сколько вы пытаетесь удалить - я делаю это здесь с математическим расширением. Это shift
происходит только в том случае, если в массиве arg есть по крайней мере 4 элемента, а затем удаляются только те первые аргументы, которые дают излишек в 4 ..
set 1 2 3 4 5
случае это сместит 1
прочь, но в set 1 2 3
случае это ничего не сместит.a=(1 2 3); echo "${a[@]: -4}"
печатает пустую строку.Если вы копируете из одного каталога в другой, вы можете использовать pax
:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir
... который применил бы sed
подстановку -style ко всем именам файлов при их копировании.
Если есть только файлы и их имена не содержат пробелов, $IFS
символов новой строки (и вы не изменили ) или символов глобуса (или некоторых непечатаемых символов с некоторыми реализациями ls
) и не начинаются с .
, то вы можете сделать это :
cp -- $(ls | tail -n 4) ~/
a_
ко ВСЕМ четырем файлам? Я пытался echo "a_$(ls | tail -n 4)"
, но это только prepends первый файл.
ls
вывод синтаксического анализа считается дурным тоном, поскольку обычно он приводит к ужасным ошибкам в именах файлов, которые содержат не только пробелы, но также символы табуляции, новые строки или другие допустимые, но "сложные" символы.
Это простое решение bash без какого-либо цикла кода. Скопируйте последние 4 файла из текущего каталога в место назначения:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
find
отдаете файлы в нужном порядке? Как вы гарантируете, что файлы, содержащие пробельные символы (включая символы новой строки) в их именах, обрабатываются правильно?
LC_ALL=C