В оболочке, как я могу tail
последний файл, созданный в каталоге?
12 ответов
Не разобрать вывод 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
Но этот список еще не говорит нам, какой из них является новейшим, поэтому нам нужно включить эту информацию. Я решил использовать параметр find -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
предполагает, что ее входные данные будут разделены символами новой строки. Если у вас есть sort
GNU, вы можете попросить ее ожидать записи с нулевым разделением, используя ключ -z
; для стандартной sort
нет решения. Меня интересует только сортировка по первым двум числам (секундам и долям секунды), и я не хочу сортировать по фактическому имени файла, поэтому я говорю sort
две вещи: во-первых, он должен учитывать точку (.
) A разделитель полей и второе, что следует использовать только первое и второе поля при рассмотрении вопроса о том, как сортировать записи.
| 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
теперь он имеет желаемый ответ, который мы ищем, прямо вверху. Осталось найти способ отбросить другие записи и убрать метку времени. К сожалению, даже head
и tail
GNU не принимают переключатели, чтобы заставить их работать на вводе с нулевым разделением. Вместо этого я использую цикл while как head
бедного человека.
| while IFS= read -r -d '' record
Сначала я отключил IFS
чтобы список файлов не подвергался разбиению по словам. Далее я предлагаю read
две вещи: не интерпретировать escape-последовательности во входных данных (-r
), и входные данные разделяются нулевым байтом (-d
); здесь пустая строка ''
используется для обозначения "без разделителя", то есть с нулевым разделением. Каждая запись будет считана в record
переменной, так что каждый раз, while
цикл while повторяется, у нее будет одна временная метка и одно имя файла. Обратите внимание, что -d
является расширением GNU; Если у вас только стандартное read
эта методика не будет работать, и у вас мало возможностей.
Мы знаем, что переменная record
состоит из трех частей, все разделены символами точки. Используя утилиту cut
, можно извлечь часть из них.
printf '%s' "$record" | cut -d. -f3-
Здесь вся запись передается printf
и оттуда по трубопроводу cut
в bash вы можете еще больше упростить это, используя строку here для cut -d. -3f- <<<"$record"
для лучшей производительности. Мы говорим cut
две вещи: во- первых с -d
, что он должен определенный разделитель для идентификации полей (как своего sort
ограничитель .
Второй cut
указывает -f
печатать только значения из определенных полей; список полей представлен в виде диапазона 3-
который указывает значение из третьего поля и из всех следующих полей. Это означает, что cut
будет читать и игнорировать все, вплоть до второго .
что он находит в записи, а затем напечатает остаток, который является частью пути к файлу.
Распечатав новый путь к файлу, продолжать движение не нужно: break
выходит из цикла, не позволяя ему перейти ко второму пути к файлу.
Единственное, что остается, - это запустить tail
на пути к файлу, возвращенному этим конвейером. Вы, возможно, заметили в моем примере, что я сделал это, заключив конвейер в подоболочку; то, что вы, возможно, не заметили, это то, что я заключил подоболочку в двойные кавычки. Это важно, потому что, наконец, даже при всех этих усилиях по обеспечению безопасности для любых имен файлов расширение без кавычек может все же сломать. Более подробное объяснение доступно, если вам интересно. Вторым важным, но легко упускаемым из виду аспектом вызова tail
является то, что я предоставил ему опцию --
перед расширением имени файла. Это будет указывать tail
что больше не задано никаких опций, и все, что следует, является именем файла, что делает безопасным обработку имен файлов, начинающихся с -
.
tail `ls -t | head -1`
Если вы беспокоитесь о именах файлов с пробелами,
tail "`ls -t | head -1`"
Если вы просто хотите увидеть изменение размера файла, вы можете использовать часы.
watch -d ls -l
Ты можешь использовать:
tail $(ls -1t | head -1)
Конструкция $()
запускает под-оболочку, которая запускает команду ls -1t
( выводит список всех файлов по порядку времени, по одному на строку) и пропускает ее через заголовок head -1
чтобы получить первую строку (файл).
Выходные данные этой команды (самый последний файл) затем передаются в tail
для обработки.
Имейте в виду, что это рискует получить каталог, если это самая последняя созданная запись каталога. Я использовал этот трюк в псевдониме для редактирования самого последнего файла журнала (из вращающегося набора) в каталоге, который содержал только эти файлы журнала.
В системах POSIX нет способа получить "последнюю созданную" запись каталога. У каждой записи каталога есть atime
, mtime
и ctime
, но, в отличие от Microsoft Windows, ctime
означает не CreationTime, а "Время последнего изменения статуса".
Таким образом, лучшее, что вы можете получить, это "привязать последний недавно измененный файл", что объясняется в других ответах. Я бы пошел для этой команды:
tail -f "$(ls -tr | sed 1q)"
Обратите внимание на кавычки вокруг команды ls
. Это заставляет фрагмент работать практически со всеми именами файлов.
В 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
.
Вероятно, есть миллион способов сделать это, но я бы сделал так:
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
Просто:
tail -f /path/to/directory/*
у меня просто отлично работает
Проблема в том, чтобы получить файлы, сгенерированные после запуска команды tail. Но если вам это не нужно (поскольку все вышеперечисленные решения не заботятся об этом), звездочка - это просто более простое решение, IMO.
tail`ls -tr | tail -1`
Кто-то опубликовал его, а затем по какой-то причине стер его, но это единственный, который работает, так что ...
tail -f `ls -tr | tail`
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Объяснение:
- ls -lt: список всех файлов и каталогов, отсортированных по времени модификации
- grep -v ^ d: исключить каталоги
- голова -2 и далее: парсинг нужного имени файла
tail "$(ls -1tr|tail -1)"