Скрипт оболонки для переміщення найстаріших файлів?


14

Як написати сценарій для переміщення лише 20 найстаріших файлів з однієї папки в іншу? Чи є спосіб схопити найдавніші файли в папці?


Включення або виключення підкаталогів? І чи слід це робити рекурсивно (у дереві каталогів)?
maxschlepzig

2
Багато файлових систем (більшість?) * Nix не зберігають дату створення, тому ви не можете точно визначити найстаріший файл. Типово доступними атрибутами є atime(останній доступ), ctime(остання зміна дозволу) та mtime(остання зміна) ... наприклад. ls -tі ФАЙНД printf "%T" використання mtime... Здається, по цьому посиланню , щоб мої ext4перегородки здатні обробляти дату створення, але lsі findта statне відповідають параметри (поки) ...
Peter.O

Відповіді:


13

Розбір результатів неls є надійним .

Замість цього використовуйте findдля пошуку файлів та sortвпорядкування їх за часовою позначкою. Наприклад:

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    # do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

Що це все робить?

Спочатку findкоманди знаходять усі файли та каталоги в поточному каталозі ( .), але не у підкаталогах поточного каталогу ( -maxdepth 1), а потім виводить:

  • Позначка часу
  • Простір
  • Відносний шлях до файлу
  • Символ NULL

Часова позначка є важливою. Специфікатор %T@формату для -printfрозбиття на T, який вказує "Час останньої модифікації" файлу (mtime) і @який вказує "Секунди з 1970 року", включаючи дробові секунди.

Простір - це лише довільний роздільник. Повний шлях до файлу полягає в тому, що ми можемо звернутися до нього пізніше, а символ NULL є термінатором, оскільки він є незаконним символом в імені файлу, і таким чином дає нам знати, що ми дійшли до кінця шляху до файл.

Я включив 2>/dev/nullтак, що файли, користувач яких не має дозволу на доступ, виключаються, але повідомлення про помилки щодо їх виключення придушуються.

Результатом findкоманди є список усіх каталогів у поточному каталозі. Список прокладений, до sortякого доручено:

  • -z Трактуйте NULL як символ термінатора рядка замість нового рядка.
  • -n Сортувати чисельно

Оскільки секунди з 1970 року завжди збільшуються, ми хочемо, щоб файл, часова марка якого був найменшим числом. Першим результатом sortбуде рядок, що містить найменшу нумеровану часову позначку. Залишилося лише витягти ім'я файлу.

Результати find, sortтрубопровід проходить через підміни процесу в whileякому він читається , як якщо б це був файл на стандартний ввід. whileв свою чергу викликає readобробку вводу.

У контексті readми встановлюємо IFSзмінну на ніщо, це означає, що пробіл не буде трактуватися належним чином як роздільник. readсказано -r, що блокує розширення втечі, і -d $'\0', що робить кінець-рядки роздільник NULL, відповідний висновок з нашого find, sortтрубопроводу.

Перший фрагмент даних, який представляє найдавніший шлях до файлу, який передує його часовій позначці та пробілу, зчитується у змінну line. Далі використовується заміщення параметрів виразом #*, який просто замінює всі символи від початку рядка до першого пробілу, включаючи пробіл, нічим. Це знімає часову позначку модифікації, залишаючи лише повний шлях до файлу.

У цей момент ім'я файлу зберігається, $fileі ви можете робити все, що завгодно. Коли ви закінчили робити що - то з $fileв whileцикл буде заяву і readкоманда буде виконана знову, витягуючи наступний фрагмент і наступне ім'я файлу.

Чи не існує простішого способу?

Ні. Простішими способами є баггі.

Якщо ви використовуєте ls -tта передаєте headабо tail(або що завгодно ), ви перейдете на файли з новими рядками у назвах файлів. Якщо mv $(anything)потім файли з пробілом в імені призведуть до поломки. Якщо mv "$(anything)"потім файли з останніми рядками в імені призведуть до поломки. Якщо ви readбез -d $'\0'цього, ви будете ламати файли з пробілами в їх іменах.

Можливо, в конкретних випадках ви точно знаєте, що більш простий спосіб достатній, але ніколи не слід писати подібні припущення в сценарії, якщо ви можете уникнути цього.

Рішення

#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    echo mv "$file" "$dest"
    let limit-=1
    [[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

Телефонуйте як:

move-oldest /mnt/backup/ /var/log/foo/ 20

Щоб перемістити найстаріші 20 файлів з /var/log/foo/до /mnt/backup/.

Зауважте, що я включаю файли та каталоги. Для файлів додайте лише -type fдо findвиклику.

Спасибі

Дякуємо enzotib та Павлу Танкову за вдосконалення цієї відповіді.


Сорт не слід використовувати -n. Принаймні в моїй версії він не сортує десяткові числа правильно. Вам або потрібно видалити крапку в даті або використовувати -printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz, дати ISO або щось інше.
l0b0

@ l0b0: Це обмеження мені відомо. Я припускаю, що достатньо не вимагати такого рівня деталізації (тобто сортування за межі .повинно бути для вас неактуальним.) Це було б чіткіше сказати sort -z -n -t. -k1.
Сорпігал

@ l0b0: ваше рішення виявляє таку ж помилку, незалежно: %TSтакож показує "дробову частину", яка була б у формі 00.0000000000, тому ви також втрачаєте деталізацію. Останні GNU sortмогли вирішити цю проблему, використовуючи -Vдля "сортування версій", який буде обробляти цей тип плаваючої точки як очікувалося.
Сорпігал

Ні, тому що я роблю рядок на "YYYY-MM-DDThh: mm: ss", а не на числовому сортуванні. Сортовий сорт не хвилює десяткових знаків, тому він повинен працювати до 10000 року :)
l0b0

@ l0b0: Сортування рядків %T@також буде працювати, тому що воно є нульовим.
Сорпігал

4

Найпростіше в zsh, де ви можете використовувати Om класифікатор світу для сортування матчів за датою (найстаріший перший), а [1,20]класифікатор може зберегти лише перші 20 матчів:

mv -- *(Om[1,20]) target/

Додайте Dкваліфікатор, якщо ви також хочете включити крапкові файли. Додайте, .якщо ви хочете відповідати лише звичайним файлам, а не каталогам.

Якщо у вас немає zsh, ось одне вкладише Perl (ви можете зробити це менше ніж 80 символів, але з додатковими витратами на ясність):

perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'

Маючи лише інструменти POSIX або навіть bash або ksh, сортування файлів за датою - це біль. Ви можете це легко зробити ls, але розбір вихідних даних lsє проблематичним, тому це працює лише в тому випадку, якщо імена файлів містять лише друковані символи, окрім нових рядків.

ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done

4

Поєднайте ls -tвихід з tailабо head.

Простий приклад, який працює лише у тому випадку, якщо всі назви файлів містять лише символи для друку, окрім пробілів та \[*?:

 mv $(ls -1tr | head -20) other_folder

1
Додайте опцію -A до ls:ls -1Atr
Arcege

1
-1, небезпечно. Тут дозвольте мені ремесло прикладу: touch $'foo\n*'. Що станеться, якщо ви виконаєте mv "$ (ls)" з тим файлом, який сидить там?
Сорпігаль

1
@Sorpigal Серйозно? Начебто слабко сказати: "Дозвольте мені привести приклад, який ви спеціально сказали, що не буде працювати. Гей, дивись, це не працює"
Michael Mrozek

1
@Sorpigal Це не погана ідея, вона працює в 99% випадків. Відповідь "якщо у вас є файли зі звичайними іменами, це працює. Якщо ви божевільна людина, яка вставляє нові рядки у свої імена файлів, це не буде". Це абсолютно правильно
Майкл Мрозек

1
@MichaelMrozek: Це погана ідея і погана, тому що іноді виходить з ладу. Якщо у вас є можливість робити те, що інколи не вдається, а що ні, слід скористатися варіантом, який не є (а той, що робить, погано). Робіть все, що вам подобається, в інтерактивному режимі, але у файлі сценаріїв і коли ви даєте поради, робіть це правильно.
Сорпігаль

3

Ви можете використовувати GNU для цього:

find -maxdepth 1 -type f -printf '%T@ %p\n' \
  | sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
  | xargs echo mv -t dest_dir

Там, де знаходять друкується час модифікації (в секундах з 1970 року) та ім'я кожного файлу поточного каталогу, вихід сортується відповідно до першого поля, 20 найстаріших фільтруються та переміщуються до dest_dir. Видаліть, echoякщо ви протестували командний рядок.


2

Ніхто ще не розмістив приклад bash, який обслуговує вбудовані символи нового рядка (вбудовані що-небудь) у ім'я файлу, ось ось один. Він переміщує 3 найстаріших (mdate) звичайних файлів

move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p\0" |sort -znk1 | { 
  while IFS= read -d $'\0' -r file; do
      printf "%s\0" "${file#*$'\t'}"
      ((--move==0)) && break
  done } |xargs -0 mv -t dest

Це фрагмент тестових даних

# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest

Ось фрагмент перевірки результатів

  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*

+1, лише корисна відповідь до моєї (і завжди добре мати дані тесту).
Сорпігал

0

Найпростіше це зробити з GNU find. Я використовую його щодня на своєму відеореєстраторі Linux для видалення записів із моєї системи відеоспостереження, старшої за добу.

Ось синтаксис:

find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;

Пам'ятайте, що findвизначається день як 24 години з моменту виконання. Тому файли, що востаннє змінені об 11 годині вечора, не будуть видалені о 1 годині ночі.

Ви навіть можете комбінувати findз cron, так делеции можуть бути заплановані автоматично, виконавши наступну команду як корінь:

crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF

Ви завжди можете отримати більше інформації про findце, ознайомившись із сторінкою керівництва:

man find

0

так як інші відповіді не відповідають моїй цілі цільових питань, ця оболонка тестується на CentOS 7:

oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.