Як отримати останні N файлів у каталозі?


15

У мене багато файлів, упорядкованих за іменем файлу в каталозі. Я хочу скопіювати остаточні N (скажімо, N = 4) файлів у свій домашній каталог. Як мені це зробити?

cp ./<the final 4 files> ~/


1
У всіх відповідях нижче, можливо, вам доведеться додати "LC_ALL = ..." перед командами за допомогою діапазонів, щоб їхні діапазони використовували потрібну для вас мову (локалі можуть змінюватися, а порядок символів може змінюватися. Наприклад, в деяких місцевостях [az] може містити всі літери алфавіту, крім "Z", як букви впорядковані: aAbBcC .... zZ. Існують інші варіанти (наголошені літери та ще більш екзотичні впорядкування Звичайний вибір:LC_ALL=C
Олів'є Дулак,

Відповіді:


23

Це легко зробити за допомогою масивів 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команду.


9

Якщо ви використовуєте, zshви можете додати до дужок ()список так званих глобальних класифікаторів, які вибирають потрібні файли. У вашому випадку це було б

cp *(On[1,4]) ~/

Тут Onсортуються назви файлів за алфавітом у зворотному порядку і [1,4]займає лише перші 4 з них.

Ви можете зробити це більш надійним, вибравши лише прості файли (за винятком каталогів, труб тощо) ., а також додавши --до cpкоманди, щоб обробляти файли, імена яких починаються з -належного, так:

cp -- *(.On[1,4]) ~

Додайте Dкваліфікатор, якщо ви також хочете розглянути приховані файли (dot-файли):

cp -- *(D.On[1,4]) ~

2
Або: *([-4,-1]).
Стефан Шазелас

2
@ StéphaneChazelas так, дозволяє уточнити майбутнім читачам, що сортування за алфавітом у звичайному напрямку ( on) є поведінкою за замовчуванням, тому не потрібно вказувати це, і -перед цифрами відлічується назви файлів знизу.
jimmij

7

Ось рішення з використанням надзвичайно простих команд 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

Петлі над кожним рядком, виконуючи команду копіювання.


6

Поки ви вважаєте, що такий тип оболонки придатний, ви можете просто зробити:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir

Це дуже схоже на bashпропоноване рішення-масив масиву, але має бути переносним до будь-якої оболонки, сумісної з POSIX. Деякі зауваження щодо обох методів:

  1. Важливо, щоб ви привели свої cpаргументи w / cp --або отримаєте одну або .крапку, або /на чолі кожного імені. Якщо цього не зробити, ви ризикуєте провідним -тире в cpпершому аргументі, який можна інтерпретувати як варіант і призвести до того, що операція не завершиться, або іншим чином непередбачувані результати.

    • Навіть якщо робота з поточним каталогом як вихідним каталогом це легко зробити, як ... set -- ./*або array=(./*).
  2. При використанні обох методів важливо переконатися, що у вашому масиві аргументів є щонайменше стільки елементів, скільки ви намагаєтесь видалити - це я роблю з математичним розширенням. 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 до всіх імен файлів під час їх копіювання.


4

Якщо є лише файли, а їхні назви не містять пробілу чи нового рядка (а ви не модифікували $IFS) або символів глобула (або деяких символів, які не можна роздрукувати з деякими реалізаціями ls), і не починати з .цього, ви можете це зробити :

cp -- $(ls | tail -n 4) ~/

Це називається підміна команд, і це дуже зручно. tldp.org/LDP/abs/html/commandsub.html#CSPARENS
iyrin

Спасибі! Це працює. Чи є можливість швидко додати префікс a_до ВСІХ чотирьох файлів? Я спробував echo "a_$(ls | tail -n 4)", але це лише попередньо перший файл.
Сіббс азартні ігри

@SibbsGambling Мій підхід для цього не підходить, але відповідь John1024 можна легко адаптувати до цього.
Hauke ​​Laging

5
У загальному випадку аналіз синтаксису lsвважається поганою формою, оскільки він, як правило, жахливо виходить з імен файлів, які містять не тільки пробіли, але й вкладки, нові рядки або інші дійсні, але "важкі" символи.
HBruijn

2
@HaukeLaging Навіть якщо це наразі не є проблемою (тому що у мене в каталозі немає "складних" іменних імен), я можу через 2 роки, і раптом все впаде на ноги ...
glglgl

1

Це просте рішення bash без коду циклу. Скопіюйте останні 4 файли з поточного режиму в місце призначення:

$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"

1
Як ви гарантуєте, що findнадає вам файли в потрібному порядку? Як ви гарантуєте, що файли, що містять символи пробілів (включаючи нові рядки) у своїх іменах, правильно обробляються?
Kusalananda
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.