як `хвіст` останнього файлу в каталозі


20

Як я можу tailотримати останній файл, створений у каталозі, в оболонці ?


1
заходьте на доводчики, програмістам потрібно замахнутися
amit

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

Справжньою проблемою тут є пошук останнього оновленого файлу в каталозі, і я вважаю, що на нього вже відповіли (або тут, або на Super User, я не можу згадати).
dmckee

Відповіді:


24

Ви НЕ розібрати висновок Ls! Розбір результатів ls є складним і ненадійним .

Якщо ви повинні зробити це, я рекомендую використовувати find. Спочатку у мене був простий приклад, щоб просто дати вам суть рішення, але оскільки ця відповідь здається дещо популярною, я вирішив переглянути цю проблему, щоб надати версію, яку можна безпечно копіювати / вставляти та використовувати з усіма вхідними даними. Ви зручно сидите? Ми почнемо з oneliner, який дасть вам останній файл у поточному каталозі:

tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"

Зараз не зовсім онлінер, чи не так? Ось він знову як функція оболонки та відформатований для легшого читання:

latest-file-in-directory () {
    find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
            sort -znr -t. -k1,2 | \
            while IFS= read -r -d '' -r record ; do
                    printf '%s' "$record" | cut -d. -f3-
                    break
            done
}

А тепер це як онлайнер:

tail -- "$(latest-file-in-directory)"

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

Застереження в цьому полягає в тому, що ім'я файлу, що закінчується в одному або декількох нових рядках, все ще не буде передано tailналежним чином. Вирішити цю проблему складно, і я вважаю достатньою, що якщо зустрінеться таке зловмисне ім'я файлу, відносно безпечна поведінка зіткнення з помилкою "Немає такого файлу" стане замість чогось більш небезпечного.

Соковиті деталі

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

Небезпека, Вілл Робінсон

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

Для сьогоднішніх цілей першим кроком є ​​вилучення з нуля списку файлів. Це досить просто, якщо у вас є така findпідтримка -print0, як GNU:

find . -print0

Але цей перелік все ще не каже нам, який із них є новітнім, тому нам потрібно включити цю інформацію. Я вибираю використовувати -printfперемикач find, який дозволяє мені вказати, які дані відображаються у висновку. Не всі версії findпідтримки -printf(це не стандартно), але GNU знайде. Якщо ви опинитеся без -printfвас, вам не доведеться покладатися на те, -exec stat {} \;в який момент ви повинні відмовитись від усієї надії на портативність, як statце теж не є стандартом. Поки що я буду переходити до того, що у вас є інструменти GNU.

find . -printf '%T@.%p\0'

Тут я прошу формату printf, %T@який є часом модифікації в секундах від початку епохи Unix, а потім періодом, а потім числом, що вказує на частки секунди. Я додаю до цього ще один період, а потім %p(який є повним шляхом до файлу), перш ніж закінчуватися нульовим байтом.

Тепер я

find . -maxdepth 1 \! -type d -printf '%T@.%p\0'

Само собою зрозуміло, але заради повного -maxdepth 1перешкоджання findперераховувати вміст підкаталогів та \! -type dпропускати каталоги, які ви навряд чи захочете tail. Поки у мене є файли в поточному каталозі з інформацією про час модифікації, тому тепер мені потрібно сортувати за тим часом модифікації.

Налаштування в правильному порядку

За замовчуванням sortочікує, що його вхід буде записаний у новий рядок. Якщо у вас є GNU, sortви можете попросити його очікувати замість нульових записів замість цього за допомогою -zперемикача; для стандарту sortнемає рішення. Мене цікавить лише сортування за першими двома числами (секундами та частками секунди), і я не хочу сортувати за фактичним іменем файлу, тому я скажу sortдві речі: По-перше, він повинен враховувати період ( .) роздільник поля по-друге, він повинен використовувати лише перше та друге поля лише при розгляді питання сортування записів.

| sort -znr -t. -k1,2

Перш за все, я поєдную три короткі варіанти, які не мають значення разом; -znrце лише стислий спосіб сказати -z -n -r). Після цього -t .(пробіл необов’язковий) вказується sortсимвол роздільника поля та -k 1,2вказуються номери полів: перше та друге ( sortрахує поля від одного, а не до нуля). Пам'ятайте, що зразок запису для поточного каталогу виглядатиме так:

1000000000.0000000000../some-file-name

Це означає sortспочатку розглядати, 1000000000а потім 0000000000при замовленні цього запису. -nОпція вказує sortвикористовувати чисельну порівняння при зіставленні цих значень, оскільки обидва значення числа. Це може не важливо, оскільки номери мають фіксовану довжину, але це не шкодить.

Інший перемикач sortпризначений -rдля "реверсу". За замовчуванням вихід чисельного сортування буде спочатку найменшими числами, -rзмінює його так, щоб він перераховував найнижчі числа останніх та найвищі числа спочатку. Оскільки ці цифри тимчасові мітки вище, це означатиме новіші, і це ставить найновіший запис на початку списку.

Просто важливі шматочки

По мірі того, як список шляхів до файлів виходить з sortнього, тепер є потрібна відповідь, яку ми шукаємо прямо вгорі. Залишилося знайти спосіб відкинути інші записи та зняти часову позначку. На жаль, навіть GNU headі tailне приймають комутатори, щоб змусити їх працювати на введенні з нульовим входом. Натомість я використовую цикл часу як різновид бідолахи head.

| while IFS= read -r -d '' record

Спочатку я скидаю, IFSщоб список файлів не піддавався поділу слів. Далі я розповім readдві речі: не інтерпретувати послідовності вхідних даних у вході ( -r), а вхід розмежований нульовим байтом ( -d); тут порожній рядок ''використовується для вказівки "немає роздільника", який також обмежений нулем. Кожен запис буде прочитаний у змінну, recordщоб кожен раз, коли whileцикл повторювався, він мав одну часову позначку та одне ім'я файлу. Зауважте, що -dце розширення GNU; якщо у вас є тільки стандарт, readця техніка не буде працювати і у вас є мало призову.

Ми знаємо, що recordзмінна має три частини до неї, усі розмежовані символами періоду. За допомогою cutутиліти можна витягти їх частину.

printf '%s' "$record" | cut -d. -f3-

Тут весь запис передається туди, printfа звідти трубопроводом cut; в Баш ви могли б спростити це далі , використовуючи тут рядок для cut -d. -3f- <<<"$record"для кращої продуктивності. Ми розповідаємо cutдві речі: Спочатку -dце повинен бути конкретний роздільник для ідентифікації полів (як і sortдля роздільника .). Друге cutдоручається -fдрукувати лише значення з певних полів; список полів подається у вигляді діапазону, 3-який вказує значення з третього поля та з усіх наступних полів. Це означає, що cutбуде читати та ігнорувати все, що стосується, включаючи друге, .яке воно знайде у записі, а потім надрукує решту, яка є частиною шляху до файлу.

Надрукувавши найновіший шлях до файлу, продовжувати продовжувати не потрібно: breakвиходить з циклу, не даючи йому перейти до другого шляху файлу.

Єдине, що залишається - це запущений tailшлях до файлу, повернутий цим конвеєром. Можливо, ви помітили в моєму прикладі, що я це зробив, уклавши трубопровід у підгрунтя; те, що ви, можливо, не помічали, - це те, що я вклав подшипник у подвійні лапки. Це важливо, оскільки, нарешті, навіть з усіма цим зусиллям, щоб бути безпечним для будь-яких імен файлів, нерозмінене розширення додаткової оболонки все ще може порушити справи. Більш детальне пояснення є , якщо ви зацікавлені. Другий важливий, але легко забуваючий аспект викликання, - tailце те, що я надав йому можливість --перед розширенням імені файлу. Це настановитьtailщо більше жодних параметрів не вказується, і все, що випливає, - це ім'я файлу, що дозволяє безпечно обробляти імена файлів, які починаються з -.


1
@AakashM: тому що ви можете отримати "дивовижні" результати, наприклад, якщо файл має "незвичні" символи у своєму імені (майже всі символи є законними).
Джон Цвінк

6
Люди, які використовують у своїх іменах спеціальні символи, заслуговують на все, що вони отримують :-)

6
Побачивши paxdiablo, що це зауваження було досить болючим, але тоді за це проголосували двоє людей! Люди, які пишуть програмне програмне забезпечення навмисно, заслуговують на все, що отримують.
Джон Цвінк

4
Отже, рішення вище не працює на osx через відсутність опції -printf у пошуку, але наступне працює лише на osx через відмінності в команді stat ... можливо, це все-таки допоможе комусьtail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
audio.zoom

2
"На жаль, навіть GNU headі tailне приймають комутатори, щоб змусити їх працювати на введенні з нульовим входом." Моя заміна head: … | grep -zm <number> "".
Каміль Маціоровський

22
tail `ls -t | head -1`

Якщо ви переживаєте за назви файлів із пробілами,

tail "`ls -t | head -1`"

1
Але що відбувається, коли в останньому файлі є пробіли або спеціальні символи? Використовуйте $ () замість `` і вкажіть свою передплату, щоб уникнути цієї проблеми.
фогг

Мені подобається це. Чисто і просто. Як має бути.

6
Бути чистим і простим легко, якщо ви жертвуєте міцними і правильними.
фогг

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

Це найбільш чисте рішення поки що. Дякую!
demisx

4

Ви можете використовувати:

tail $(ls -1t | head -1)

$()Конструкція починає подоболочкі , яка запускає команду ls -1t(лістинг всіх файлів в порядку часу, по одному в рядку) і трубопроводів , що через , head -1щоб отримати перший рядок (файл).

Вихід цієї команди (найновіший файл) потім передається для tailобробки.

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


Це -1не потрібно, lsробить це для вас, коли він знаходиться в трубі. Порівняйте lsі ls|cat, наприклад.
Призупинено до подальшого повідомлення.

Це може бути справа в Linux. У "справжньому" Unix процеси не змінили свою поведінку залежно від того, куди йде їх вихід. Це зробило б налагодження на трубопроводі справді дратівливим :-)

Гммм, не впевнений, що це правильно - ISTR повинен видавати "ls -C", щоб отримати вихід у форматі стовпців під 4.2BSD, коли трубопровід виводиться через фільтр, і я впевнений, що LS під Solaris працює так само. Що таке "єдиний, справжній Unix"?

Цитати! Цитати! Імена файлів мають пробіли!
Норман Рамзі

@TMN: Єдиний вірний спосіб Unix - це не покладатися на ls для споживачів, які не є людьми. "Якщо висновок призначений для терміналу, формат визначається реалізацією." - це специфікація. Якщо ви хочете бути впевнені, вам слід сказати ls -1 або ls -C.
фогг

4

У системах POSIX немає можливості отримати "останній створений" запис каталогу. Кожен запис каталогу має atime, mtimeі ctime, але в відміну від Microsoft Windows, то ctimeне означає , що CreationTime, але «Час останньої зміни статусу».

Тож найкраще, що ви можете отримати, це "виправити останній нещодавно змінений файл", що пояснено в інших відповідях. Я б пішов на цю команду:

хвіст -f "$ (ls -tr | sed 1q)"

Зверніть увагу на лапки навколо lsкоманди. Це змушує фрагмент працювати майже з усіма іменами файлів.


Хороша робота. Прямо до точки. +1
Норман Рамзі

4

Я просто хочу побачити зміну розміру файлу, яку ви можете використовувати watch.

watch -d ls -l

3

В zsh:

tail *(.om[1])

Дивіться: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , тут mпозначається час модифікації m[Mwhms][-|+]n, а попередній oозначає, що він сортується одним способом ( Oсортує його іншим способом). В .означає тільки регулярні файли. У дужках [1]вибирається перший предмет. Вибрати три використання [1,3], щоб отримати найдавніше використання [-1].

Це добре короткий і не використовує ls.


1

Напевно, є мільйон способів це зробити, але спосіб, як я це зробив би, це такий:

tail `ls -t | head -n 1`

Біти між задніми позначками (цитата як символи) інтерпретуються, а результат повертається в хвіст.

ls -t #gets the list of files in time order
head -n 1 # returns the first line only

2
Повернення - це зло. Використовуйте натомість $ ().
Вільям Перселл

1

Простий:

tail -f /path/to/directory/*

працює для мене просто чудово.

Проблема полягає в тому, щоб отримати файли, що генеруються після запуску хвостової команди. Але якщо вам це не потрібно (оскільки всі рішення, наведені вище, це не стосується), зірочка - це просто просте рішення, IMO.



0

Хтось розмістив його, а потім чомусь стерв його, але це єдине, що працює, тож ...

tail -f `ls -tr | tail`

ви повинні виключити каталоги, чи не так?
amit

1
Я опублікував це спочатку, але я видалив його, оскільки я згоден з Sorpigal, що аналіз результатів аналізу lsне є найрозумнішим ділом ...
ChristopheD

Мені це потрібно швидко і брудно, в ньому немає каталогів. Отже, якщо ви додасте свою відповідь, я прийму цю
Itay Moav -Malimovka

0
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`

Пояснення:

  • ls -lt: Список усіх файлів і каталогів, відсортованих за часом модифікації
  • grep -v ^ d: виключити каталоги
  • голова -2 далі: розбір потрібного імені файлу

1
+1 для розумних, -2 для розбору вихідних даних, -1 для не цитування нижньої частини, -1 для магічного припущення "поле 8" (це не портативно!) І нарешті -1 для занадто розумного . Загальний бал: -4.
Фогг

@Sorpigal Погодився. Хоча щасливим, що ти поганий приклад.
amit

да не уявляв собі , що було б неправильно по так багатьма пунктами
Аміт

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.