У мене багато файлів, упорядкованих за іменем файлу в каталозі. Я хочу скопіювати остаточні 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=(./*).При використанні обох методів важливо переконатися, що у вашому масиві аргументів є щонайменше стільки елементів, скільки ви намагаєтесь видалити - це я роблю з математичним розширенням. shiftВідбувається тільки якщо є принаймні 4 елементів в масиві Агда - і то тільки зміщається той перший Args , які роблять надлишок 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)", але це лише попередньо перший файл.
lsвважається поганою формою, оскільки він, як правило, жахливо виходить з імен файлів, які містять не тільки пробіли, але й вкладки, нові рядки або інші дійсні, але "важкі" символи.
Це просте рішення bash без коду циклу. Скопіюйте останні 4 файли з поточного режиму в місце призначення:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
findнадає вам файли в потрібному порядку? Як ви гарантуєте, що файли, що містять символи пробілів (включаючи нові рядки) у своїх іменах, правильно обробляються?
LC_ALL=C