Розбір результатів не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 та Павлу Танкову за вдосконалення цієї відповіді.