У мене багато файлів, упорядкованих за іменем файлу в каталозі. Я хочу скопіювати остаточні 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