Get-Content -wait не работает, как описано в документации

Я заметил, что когда Get-Content path/to/logfile -Wait, результат фактически не обновляется каждую секунду, как объясняет документация. Если я перейду в Проводник Windows в папку, где находится файл журнала, и обновите папку, то Get-Content выведет последние изменения в файл журнала.

Если я попробую tail -f с cygwin в том же файле журнала (не в то же время, чем при попытке Get-Content), тогда он будет хвостом, как и следовало ожидать, обновив реальное время без меня. p >

Есть ли у кого-нибудь идеи, почему это происходит?

Ответ 1

Изменить: Бернхард Кениг сообщает в комментариях, что это, наконец, было исправлено в Powershell 5.

Вы совершенно правы. Параметр -Wait в Get-Content ожидает, пока файл не будет закрыт, прежде чем он прочитает больше контента. Это можно продемонстрировать в Powershell, но может оказаться сложным, чтобы получить право в виде циклов, таких как:

while (1){
get-date | add-content c:\tesetfiles\test1.txt 
Start-Sleep -Milliseconds 500
}

будет открывать и закрывать выходной файл каждый раз за цикл.

Чтобы продемонстрировать проблему, откройте два окна Powershell (или две вкладки в ISE). В один введите эту команду:

PS C:\> 1..30 | % { "${_}: Write $(Get-Date -Format "hh:mm:ss")"; start-sleep 1 } >C:\temp\t.txt

Это будет работать в течение 30 секунд, записывая по 1 строке в файл каждую секунду, но не закрывает и не открывает файл каждый раз.

В другом окне используйте Get-Content для чтения файла:

get-content c:\temp\t.txt -tail 1 -wait | % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }

С опцией -Wait вам нужно использовать Ctrl + C, чтобы остановить выполнение этой команды, которая три раза ждет несколько секунд после каждого из первых двух и более длинное ожидание после того, как третий дал мне это выход:

PS C:\> get-content c:\temp\t.txt -tail 1 -wait | % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }
8: Write 12:15:09 read at 12:15:09

PS C:\> get-content c:\temp\t.txt -tail 1 -wait | % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }
13: Write 12:15:14 read at 12:15:15

PS C:\> get-content c:\temp\t.txt -tail 1 -wait | % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }
19: Write 12:15:20 read at 12:15:20
20: Write 12:15:21 read at 12:15:32
21: Write 12:15:22 read at 12:15:32
22: Write 12:15:23 read at 12:15:32
23: Write 12:15:24 read at 12:15:32
24: Write 12:15:25 read at 12:15:32
25: Write 12:15:26 read at 12:15:32
26: Write 12:15:27 read at 12:15:32
27: Write 12:15:28 read at 12:15:32
28: Write 12:15:29 read at 12:15:32
29: Write 12:15:30 read at 12:15:32
30: Write 12:15:31 read at 12:15:32

Из этого я ясно вижу:

  • Каждый раз, когда выполняется команда, он получает последнюю строку, записанную в файл. то есть нет проблем с кешированием, и никакие буферы, нуждающиеся в очистке.
  • Только одна строка считывается, а затем не выводится дополнительный вывод, пока команда, выполняющаяся в другом окне, не завершится.
  • После завершения всех оставшихся строк отображаются вместе. Это должно было быть вызвано исходной программой, закрывающей файл.

Также, когда я повторил упражнение с помощью команды Get-Content, запущенной в двух других окнах, одно окно читало строку 3, а затем просто ждало, другое окно читало строку 6, поэтому строка определенно записывается в файл.

Кажется довольно убедительным, что опция -Wait ожидает события закрытия файла, не дожидаясь объявления за 1 секунду. Документация неверна.

Edit: Я должен добавить, поскольку Ади Инбар кажется настойчивым, что я ошибаюсь, что примеры, которые я здесь приводил, используют Powershell только так, как это было наиболее подходящим для обсуждения Powershell. Я также проверил с помощью Python, что поведение точно так, как я описал:

Содержимое, записанное в файл, может быть прочитано новой командой Get-Content -Wait, сразу же при условии, что приложение сбросило его буфер.

Экземпляр Powershell с использованием Get-Content -Wait не будет отображать новое содержимое в файле, который записывается, даже если другой экземпляр Powershell, начатый позже, видит более поздние данные. Это убедительно доказывает, что данные доступны для Powershell, а Get-Content -Wait не выполняет опрос с интервалом в 1 секунду, но ждет какого-либо события триггера, прежде чем он будет искать данные.

Размер файла, о котором сообщает dir, обновляется, когда строки добавляются, поэтому это не случай, когда Powershell ожидает обновления размера записи в каталоге.

Когда процесс записи файла закрывается, Get-Content -Wait отображает новое содержимое почти мгновенно. Если бы он ожидал, пока данные будут сброшены на диск, будет досрочно, пока Windows не сбросит его в кэш диска.

@AdiInbar, боюсь, вы не понимаете, что делает Excel при сохранении файла. Присмотритесь. Если вы редактируете test.xlsx, тогда есть скрытый файл ~test.xlsx в той же папке. Используйте dir ~test.xlsx -hidden | select CreationTime, чтобы увидеть, когда он был создан. Сохраните файл, и теперь test.xlsx будет иметь время создания от ~test.xlsx. Другими словами, сохранение в Excel сохраняет файл ~, а затем удаляет оригинал, переименовывает файл ~ в исходное имя и создает новый файл ~. Там много открытий и закрытий.

Перед тем, как сохранить, у вас есть файл, который вы открываете, и после того, как файл открыт, но его другой файл. Я думаю, что Excel слишком сложный сценарий, чтобы точно сказать, какие триггеры Get-Content отображают новый контент, но я уверен, что вы неправильно интерпретировали его.

Ответ 2

Похоже, Powershell контролирует свойство файла Last Modified. Проблема в том, что "по соображениям производительности" метаданные NTFS, содержащие это свойство, не обновляются автоматически, за исключением определенных случаев.

Один cirumstance - это когда дескриптор файла закрыт (следовательно, наблюдения @Duncan). Другим является то, когда информация о файле запрашивается напрямую, следовательно, поведение обновления проводника, упомянутое в вопросе.

Вы можете наблюдать корреляцию, наблюдая, как Powershell контролирует журнал с помощью Get-Content -Wait и открывает Explorer в папке в подробном представлении с столбцом Last Modified. Обратите внимание, что Last Modified не обновляется автоматически по мере изменения файла.

Теперь получите свойства файла в другом окне. Например. в командной строке type файл. Или откройте другое окно Explorer в той же папке и щелкните правой кнопкой мыши файл и получите его свойства (для меня достаточно щелчка правой кнопкой мыши). Как только вы это сделаете, первое окно Explorer автоматически обновит столбец Last Modified, и Powershell заметит обновление и догонит журнал. В Powershell достаточно коснуться свойства LastWriteTime:

(Get-Item file.log).LastWriteTime = (Get-Item file.log).LastWriteTime

или

(Get-Item file.log).LastWriteTime = Get-Date

Итак, теперь это работает для меня:

Start-Job {
  $f=Get-Item full\path\to\log
  while (1) {
    $f.LastWriteTime = Get-Date
    Start-Sleep -Seconds 10
  }
}
Get-Content path\to\log -Wait

Ответ 3

Можете ли вы рассказать нам, как воспроизвести это?

Я могу запустить этот script на одном сеансе PS:

get-content c:\testfiles\test1.txt -wait

а в другом сеансе:

while (1){
get-date | add-content c:\tesetfiles\test1.txt 
Start-Sleep -Milliseconds 500
}

И я вижу, что новые записи записываются в первый сеанс.

Ответ 4

Похоже, что get-content работает только в том случае, если он проходит через windows api и версии для добавления в файл различаются.

program.exe > output.txt

И затем

get-content output.txt -wait

Не будет обновляться. Но

program.exe | add-content output.txt

будет работать с.

get-content output.txt -wait    

Итак, я думаю, это зависит от того, как приложение выводит.

Ответ 5

Я могу заверить, что Get-Content -Wait обновляется каждую секунду и показывает изменения при изменении файла на диске. Я не уверен, что tail -f работает по-другому, но на основе вашего описания я точно уверен, что эта проблема связана не с PowerShell, а с кэшированием записи. Я не могу исключить, что log4net выполняет кэширование, но я сильно подозреваю, что кэширование на уровне ОС является виновником по двум причинам:

  • Документация для log4j/log4net говорит, что она поместила буфер после каждой операции добавления по умолчанию, и я полагаю, что если бы вы явно настроили его не сбрасывать после каждого добавления, вы бы знали об этом.
  • Я знаю, что обновление Windows Explorer запускает сброс буфера записи, если какие-либо файлы в каталоге изменились. Это потому, что на самом деле читает содержимое файла, а не только метаданные, для предоставления расширенной информации, такой как миниатюры и предварительные просмотры, а операция чтения приводит к сбросу буфера записи. Таким образом, если вы просматриваете задержанные обновления каждый раз, когда вы обновляете каталог журнала в проводнике Windows, это сильно указывает на это.

Попробуйте это: Откройте диспетчер устройств, разверните Дисковые накопители node, откройте Свойства диска, на котором хранится файл журнала, перейдите на вкладку Политики, и снимите флажок Включить кэширование записи на устройстве. Я думаю, вы обнаружите, что Get-Content -Wait теперь покажет вам изменения по мере их возникновения.

Что касается того, почему tail -f показывает вам изменения сразу, как есть, я могу только догадываться. Возможно, вы используете его для мониторинга файла журнала на другом диске, или, возможно, Cygwin запрашивает частые сбрасывания, когда вы используете tail -f, чтобы решить эту проблему.


UPDATE:

Дункан прокомментировал ниже, что это проблема с PowerShell, и опубликовал ответ, в котором утверждается, что Get-Content -Wait не выводит новые результаты, пока файл не будет закрыт, вопреки документации.

Однако, основываясь на уже установленной и последующей проверке информации, я убедительно подтвердил, что она не дожидается закрытия файла, но выводит новые данные, добавленные в файл, как только он записывается на диск, и что проблема OP видит почти определенно из-за буферизации записи.

Чтобы доказать это, пусть факты будут представлены в откровенный мир:

  • Я создал электронную таблицу Excel и выполнил Get-Content -Wait по файлу .xlsx. Когда я вводил новые данные в электронную таблицу, Get-Content -Wait не выдавал новый вывод, который ожидается, пока новая информация будет только в ОЗУ, а не на диске. Тем не менее, всякий раз, когда я сохранял таблицу после добавления данных, новый вывод производился немедленно.

    Excel не закрывает файл при его сохранении. Файл остается открытым до закрытия окна из Excel или выхода из Excel. Вы можете проверить это, пытаясь удалить, переименовать или иным образом изменить файл .xlsx после его сохранения, в то время как окно все еще открыто в Excel.

  • OP заявила, что получает новый результат, когда обновляет папку в проводнике Windows. Обновление списка папок не закрывает файл. Он сбрасывает буфер записи, если какой-либо из файлов был изменен. Это потому, что он должен читать атрибуты файла, и эта операция очищает буфер записи. Я попытаюсь найти некоторые ссылки для этого, но, как я заметил выше, я знаю, что это правда.

  • Я проверил это поведение, выполнив следующую измененную версию теста Duncan, которая выполняется для 1000 итераций вместо 50, и отображает прогресс на консоли, чтобы вы могли точно отслеживать, как вывод в вашем окне Get-Content -Wait относится к данным, которые конвейер добавил в файл:

    1..1000 | %{"${_}: Write $(Get-Date -Format "hh:mm:ss")"; Write-Host -NoNewline "$_..."; Start-Sleep 1} > .\gcwtest.txt
    

    Пока это было запущено, я запустил Get-Content -Wait .\gcwtest.txt в другое окно и открыл каталог в Проводнике Windows. Я обнаружил, что если я обновляюсь, то больше выход будет производиться в любое время, когда размер файла в KB изменяется, а иногда, но не всегда, даже если ничего не изменилось. (Подробнее о последствиях этой несогласованности позже...)

  • Используя тот же тест, я открыл третье окно PowerShell и заметил, что все следующие триггеры мгновенно обновляются в списке Get-Content -Wait:

    • Список содержимого файла с простым старым Get-Content .\gcwtest.txt

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

      Например, (gi .\gcwtest.txt).lastwritetime запускает несколько раз больше. С другой стороны, (gi .\gcwtest.txt).mode или (gi .\gcwtest.txt).directory запускают больше выходных данных каждый раз каждый, но не если вы их повторяете. Также обратите внимание на следующее:

      & raquo;  . Это поведение не соответствует 100%. Иногда чтение Режим или Каталог не запускает больше вывода в первый раз, но это происходит, если вы повторяете операцию. Все последующие повторы после первого, запускающего обновленный вывод, не имеют эффекта.

      & raquo;   Если вы повторите тест, чтение атрибутов, которые являются одинаковыми, не вызывает вывод, если вы не удалите файл .txt перед повторным запуском конвейера. Фактически, иногда даже (gi .\gcwtest.txt).lastwritetime не выдает больше вывода, если вы повторяете тест без удаления gcwtest.txt.

      & raquo;   Если вы выдаете (gi .\gcwtest.txt).lastwritetime несколько раз за одну секунду, только первый запускает вывод, т.е. только когда результат изменился.

    • Открытие файла в текстовом редакторе. Если вы используете редактор, который держит дескриптор файла открытым (в блокноте нет), вы увидите, что закрытие файла без сохранения не приводит к тому, что Get-Content -Wait выводит строки, добавленные конвейером, с момента открытия файла в редакторе.

    • Вкладка - завершение имени файла

  • После нескольких попыток тестирования несколько раз вы обнаружите, что Get-Content -Wait периодически выводит больше строк на оставшуюся часть выполнения конвейера, даже если вы ничего не делаете. Не одна строка за раз, а в партиях.

  • Непоследовательность в самом поведении указывает на промывку буфера, которая возникает в соответствии с переменными критериями, которые трудно предсказать, в отличие от закрытия, что происходит в четких и последовательных обстоятельствах.

Вывод: Get-Content -Wait работает точно так же, как рекламируется. Новый контент отображается, как только он физически записывается в файл на диске *.

Следует отметить, что мое предложение отключить кэширование записи на диске не для теста выше, т.е. Оно не привело к "Get-Content -Wait", отображающему новые строки, как только они будут добавлены в текстовый файл по конвейеру, поэтому, возможно, буферизация, ответственная за выходную задержку, происходит на уровне файловой системы или ОС, в отличие от кэша записи на диск. Однако буферизация записи, очевидно, является объяснением поведения, наблюдаемого в вопросе ОП.

* Я не буду подробно разбираться в этом, так как это выходит за рамки вопроса, но Get-Content -Wait ведет себя странно, если вы добавляете контент в файл не в конце. Он отображает данные с конца файла, равные по размеру, количеству добавляемых данных. Недавно отображаемые данные обычно повторяют данные, которые были ранее отображены, и могут включать или не включать какие-либо новые данные в зависимости от того, превышает ли размер новых данных размер данных, следующих за ним.

Ответ 6

Я столкнулся с тем же вопросом, пытаясь смотреть WindowsUpdate.log в реальном времени. Хотя это и не идеально, приведенный ниже код позволил мне отслеживать прогресс. -Wait не работает из-за тех же ограничений записи файлов, о которых говорилось выше.

Отображает последние 10 строк, спящий на 10 секунд, очищает экран и снова отображает последние 10. CTRL + C, чтобы остановить поток.

 while(1){
Get-Content C:\Windows\WindowsUpdate.log -tail 10 
    Start-Sleep -Seconds 10
    Clear 
    }