как `хвост` последний файл в каталоге

20

В оболочке, как я могу tailпоследний файл, созданный в каталоге?

Итай Моав -Малимовка
источник
1
давай ближе, программистам надо хвастаться!
Амит
Закрытие только для перехода к суперпользователю или к серверу. Вопрос будет жить там, и больше людей, которые могут быть заинтересованы, найдут его.
Мнемент
Настоящая проблема здесь - это найти самый последний файл обновления в каталоге, и я считаю, что на этот вопрос уже был дан ответ (я не могу вспомнить ни здесь, ни у суперпользователя).
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. При обработке любого списка путей к файлам важно использовать только нулевое значение в качестве разделителя и при передаче даже одного пути к файлу от одной программы к другой делать это таким образом, чтобы не задушить произвольные байты. Существует много, казалось бы, правильных способов решения этой и других проблем, которые терпят неудачу, предполагая (даже случайно), что в именах файлов не будет ни новых строк, ни пробелов. Ни одно предположение не является безопасным.

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

find . -print0

Но этот список еще не говорит нам, какой из них является новейшим, поэтому нам нужно включить эту информацию. Я решил использовать -printfпереключатель поиска, который позволяет мне указать, какие данные появляются в выводе. Не все версии findподдержки -printf(это не стандарт), но GNU find делает. Если вы окажетесь без -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две вещи: во-первых, он должен рассматривать period ( .) как разделитель полей и второе, что оно должно использовать только первое и второе поля при рассмотрении того, как сортировать записи.

| 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не принимают переключатели, чтобы заставить их работать на вводе с нулевым разделением. Вместо этого я использую цикл while как вид бедного человека head.

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

Сначала я сбрасываю, IFSчтобы список файлов не подвергался разбиению по словам. Далее я расскажу readдве вещи: не интерпретируйте escape-последовательности в input ( -r), а вход ограничивается нулевым byte ( -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что больше никаких опций не указывается, и все, что следует за ним, является именем файла, что позволяет безопасно обрабатывать имена файлов, начинающиеся с -.

phogg
источник
1
@AakashM: потому что вы можете получить «неожиданные» результаты, например, если в имени файла есть «необычные» символы (почти все символы являются допустимыми).
Джон Цвинк
6
Люди, которые используют специальные символы в именах своих файлов, заслуживают всего, что они получают :-)
6
Видеть, как Паксдиабло сделал это замечание, было достаточно больно, но потом два человека проголосовали за него! Люди, которые пишут программное обеспечение с ошибками, намеренно заслуживают всего, что получают.
Джон Цвинк
4
Таким образом, вышеприведенное решение не работает на osx из-за отсутствия опции -printf в find, но следующее работает только на 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
Ну, это действительно зависит от того, что ты делаешь. Решение, которое всегда работает везде, для всех возможных имен файлов, очень хорошо, но в стесненной ситуации (например, файлы журналов с известными непонятными именами) может оказаться ненужным.
Это самое чистое решение на сегодняшний день. Спасибо!
Демискс
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, а «Время последнего изменения статуса».

Таким образом, лучшее, что вы можете получить, это «привязать последний недавно измененный файл», что объясняется в других ответах. Я бы пошел для этой команды:

tail -f "$ (ls -tr | sed 1q)"

Обратите внимание на кавычки вокруг lsкоманды. Это заставляет фрагмент работать практически со всеми именами файлов.

Роланд Иллиг
источник
Хорошо сделано. Прямо к сути. +1
Норман Рэмси
4

Если вы просто хотите увидеть изменение размера файла, вы можете использовать часы.

watch -d ls -l
tpal
источник
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`

Биты между обратными чертами (кавычки, подобные символам) интерпретируются, и результат возвращается в tail.

ls -t #gets the list of files in time order
head -n 1 # returns the first line only
iblamefish
источник
2
Обратные следы являются злом. Вместо этого используйте $ ().
Уильям Перселл
1

Простой:

tail -f /path/to/directory/*

у меня просто отлично работает

Проблема в том, чтобы получить файлы, сгенерированные после запуска команды tail. Но если вам это не нужно (поскольку все вышеперечисленные решения не заботятся об этом), звездочка - это просто более простое решение, IMO.

bruno.braga
источник
0
tail`ls -tr | tail -1`
user22644
источник
Вы забыли место там!
Blacklight Shining
0

Кто-то опубликовал его, а затем по какой-то причине стер его, но это единственный, который работает, так что ...

tail -f `ls -tr | tail`
Итай Моав -Малимовка
источник
Вы должны исключить каталоги, не так ли?
Амит
1
Первоначально я опубликовал это, но удалил его, так как согласен с Sorpigal, что синтаксический анализ выходных данных lsне самая разумная вещь ...
ChristopheD
Мне это нужно быстро и грязно, в нем нет каталогов. Итак, если вы добавите свой ответ, я приму этот
Итай Моав -Малимовка
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 для разбора вывода ls, -1 для не цитирования подоболочки, -1 для магического предположения "поля 8" (оно не переносимо!) И, наконец, -1 для слишком умных . Общий балл: -4.
Фогг
@Sorpigal Согласен. Рад быть плохим примером.
Амит
да не предполагал, что это будет неправильно по многим причинам
amit