【问题标题】:Get-Content -wait not working as described in the documentationGet-Content -wait 不按文档中的说明工作
【发布时间】:2013-11-12 01:17:10
【问题描述】:

我注意到当Get-Content path/to/logfile -Wait 时,输出实际上并没有按照文档说明的那样每秒刷新一次。如果我在 Windows 资源管理器中进入日志文件所在的文件夹并刷新该文件夹,那么 Get-Content 将输出对日志文件的最新更改。

如果我在同一个日志文件上尝试 tail -f 和 cygwin(与尝试 get-content 时不同时),那么它会像预期的那样拖尾,无需我做任何事情即可实时刷新。

有人知道为什么会这样吗?

【问题讨论】:

标签: powershell powershell-3.0


【解决方案1】:

编辑: Bernhard König 在 cmets 中报告说,这最终已在 Powershell 5 中得到修复。

你说的很对。 Get-Content 上的 -Wait 选项会等到文件关闭后再读取更多内容。可以在 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 来停止命令,以便运行该命令 3 次,在前两次之后等待几秒钟在第三个给我这个输出之后更长的等待:

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

从这里我可以清楚地看到:

  1. 每次运行命令时,它都会获取写入文件的最新行。即缓存没有问题,也没有需要刷新的缓冲区。
  2. 只读取一行,然后在另一个窗口中运行的命令完成之前不会出现进一步的输出。
  3. 一旦完成,所有未决行将一起出现。这一定是由关闭文件的源程序触发的。

另外,当我在另外两个窗口中运行 Get-Content 命令重复练习时,一个窗口读取第 3 行,然后等待,另一个窗口读取第 6 行,因此该行肯定被写入文件。

-Wait 选项正在等待文件关闭事件,而不是等待广告中的 1 秒,这似乎很有定论。文档有误。

编辑: 我应该补充一点,因为 Adi Inbar 似乎坚持认为我错了,我在这里给出的示例仅使用 Powershell,因为这似乎最适合 Powershell 讨论。我还使用 Python 验证了行为与我描述的完全一样:

如果应用程序已刷新其缓冲区,则写入文件的内容可由新的Get-Content -Wait 命令立即读取。

使用Get-Content -Wait 的 Powershell 实例将不会在正在写入的文件中显示新内容,即使稍后启动的另一个 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 来显示新内容,但我敢肯定你误解了它。

【讨论】:

  • 一些对位点来详细说明我对您的评论的回应。首先,澄清一下。听起来您误解了我所说的关于缓冲的内容,因为启动和停止 Get-Content -Wait 的测试没有抓住重点。我没有说Get-Content -Wait 的控制台输出正在被缓冲,我说的是管道输出到文本文件的写缓冲。由于仅当文件内容在磁盘上发生更改时才会显示新行,因此只要正在缓冲对文本文件的写入,Get-Content -Wait 就不会显示新行。
  • 其次,您需要查看更广泛的数据集。您推断管道完成时出现剩余行的原因是文件已关闭,但证据绝不是决定性的,实际上推断是不正确的。 Get-Content -Wait写入磁盘时立即显示附加数据,而不仅仅是在文件关闭时。为了证明这一点,请在 Excel 文档上执行 Get-Content -Wait。当您将数据添加到电子表格时,一旦您从 Excel 中保存,您就会看到 Get-Content -Wait 吐出更多数据。
  • ...Excel 不会在您保存文件时关闭文件,只有在您关闭文件窗口或退出程序时(您可以通过在提示符处尝试删除它来证明它仍然打开) Excel)。
  • 第三,您的假设直接与 OP 在他的问题中指出的行为相矛盾,我已经通过自己的测试确认了这一点:刷新 Windows 资源管理器中的文件夹会导致 Get-Content -Wait 输出具有自上次生成输出以来,已将其添加到文本文件中。我已验证 OP 的这一观察结果适用于您描述的测试。刷新文件夹不会关闭文件,但如果文件夹的任何内容发生更改,它会刷新写入缓存。
  • 升级到 Powershell 5 后,现在终于可以正常工作了。
【解决方案2】:

Powershell 似乎正在监视文件的 Last Modified 属性。问题是“出于性能原因”包含此属性的 NTFS 元数据是 not automatically updated,除非在某些情况下。

一种情况是文件句柄关闭时(因此@Duncan's observations)。另一种是直接查询文件信息时,问题中提到的资源管理器刷新行为。

您可以通过让 Powershell 使用 Get-Content -Wait 监视日志并在文件夹中打开资源管理器并在详细视图中打开 Last Modified 列来观察相关性。请注意,Last Modified 不会随着文件的修改而自动更新。

现在在另一个窗口中获取文件的属性。例如。在命令提示符处,type 文件。或者在同一个文件夹中打开另一个资源管理器窗口,然后右键单击该文件并获取其属性(对我来说,只需右键单击就足够了)。一旦你这样做,第一个资源管理器窗口将自动更新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】:

你能告诉我们如何重现它吗?

我可以在一个 PS 会话上启动这个脚本:

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

这在另一个会话中:

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

我看到新条目是在第一个会话中编写的。

【讨论】:

  • 我尝试了您的测试场景并且确实有效。我的特殊情况是 .Net 应用程序通过 log4net 将文本写入日志文件。那是“-wait”无法处理的文件。我的devenv是Windows 2008。我最初认为这是log4net将内容刷新到文件的方式,但如果是这种情况,为什么tail -f通过cygwin设法对日志文件进行适当的拖尾......
  • 我不知道。该测试正在执行一系列写入操作,每次打开和关闭文件。如果 log4net 进行流式写入,那将是场景中的一个差异。
  • @mjolinor,非常尊重 10K 代表用户和知名的 Powershell 专家:您认为这是一个答案,还是更适合作为评论?
  • 我其实是开始做评论的,但我想我应该发布我用来测试的代码。
  • @julio.g 我在 Windows 10 中的 log4net 日志文件上得到了相同的结果“-wait”。我认为 log4net 写入文件的方式有点特别。
【解决方案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    

所以我猜这取决于应用程序的输出方式。

【讨论】:

  • 实际上管道进入add-content 至少在Powershell v3.0 中不起作用,而是抱怨The process cannot access the file 不像重定向Add-Content 似乎只打开文件。如果首先启动Get-Content,那么它会在 Add-Content 终止时获得所有输出。多次调用 Add-Content 的循环将起作用。
【解决方案5】:

我可以向您保证Get-Content -Wait 会每秒刷新一次,并在磁盘上的文件更改时向您显示更改。我不确定tail -f 的做法有何不同,但根据您的描述,我几乎可以肯定此问题与 PowerShell 无关,而是与写入缓存有关。我不能排除 log4net 正在做缓存的可能性,但我强烈怀疑操作系统级别的缓存是罪魁祸首,原因有两个:

  1. log4j/log4net 的文档说,默认情况下,它会在每次追加操作后刷新缓冲区,我认为如果您已明确将其配置为在每次追加后不刷新,您就会意识到这一点。
  2. 我知道,如果目录中的任何文件发生更改,刷新 Windows 资源管理器会触发写入缓冲区刷新。那是因为它实际上是读取文件内容,而不仅仅是元数据,以便提供缩略图和预览等扩展信息,而读取操作会导致写入缓冲区刷新。因此,如果您每次在 Windows 资源管理器中刷新日志文件的目录时都看到延迟更新,那么这强烈指向这个方向。

试试这个:打开设备管理器,展开磁盘驱动器节点,打开存储日志文件的磁盘的属性,切换到策略选项卡,然后取消选中在设备上启用写入缓存。我想您会发现 Get-Content -Wait 现在会在更改发生时向您显示更改。

至于为什么tail -f 会立即向您显示更改,我只能推测。也许您正在使用它来监控不同驱动器上的日志文件,或者 Cygwin 可能会在您运行 tail -f 时请求频繁刷新,以解决这个问题。


更新:

Duncan 在下面评论说这是 PowerShell 的一个问题,并发布了一个答案,声称 Get-Content -Wait 在文件关闭之前不会输出新结果,这与文档相反。

但是,根据已经建立的信息和进一步的测试,我已经确定不会等待文件关闭,而是在文件关闭后立即输出添加到文件中的新数据写入磁盘,并且 OP 看到的问题几乎肯定是由于写入缓冲造成的。

为了证明这一点,将事实提交给一个坦率的世界:

  • 我创建了一个 Excel 电子表格,并针对 .xlsx 文件运行 Get-Content -Wait。当我在电子表格中输入新数据时,Get-Content -Wait 没有产生新的输出,这是预期的,而新信息仅在 RAM 中而不在磁盘上。但是,每当我在添加数据后保存电子表格时,都会立即生成新的输出。

    Excel 不会在您保存文件时关闭文件。在您从 Excel 中关闭窗口或退出 Excel 之前,该文件将保持打开状态。您可以在保存后尝试删除、重命名或以其他方式修改 .xlsx 文件来验证这一点,同时该窗口在 Excel 中仍处于打开状态。

  • OP 表示,当他在 Windows 资源管理器中刷新文件夹时,他会获得新的输出。刷新文件夹列表不会关闭文件。如果任何文件发生更改,它确实刷新写入缓冲区。那是因为它必须读取文件的属性,并且此操作会刷新写入缓冲区。我会尝试为此找到一些参考资料,但正如我上面提到的,我知道这是真的。

  • 我通过运行 Duncan 测试的以下修改版本来验证此行为,该测试运行 1,000 次迭代而不是 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 在第一次时会触发更多输出,但如果您重复它们则不会。还要注意以下几点:

      »   此行为并非 100% 一致。有时,第一次读取 ModeDirectory 不会触发更多输出,但如果您重复该操作就会触发。触发更新输出的第一个重复之后的所有后续重复均无效。

      »  如果您重复测试,读取相同的属性不会触发输出,除非您在再次运行管道之前删除 .txt 文件。事实上,如果您在不删除 gcwtest.txt 的情况下重复测试,有时即使 (gi .\gcwtest.txt).lastwritetime 也不会触发更多输出。

      »   如果您在一秒钟内多次发出(gi .\gcwtest.txt).lastwritetime,则只有第一个触发输出,即仅当结果发生变化时。

    • 在文本编辑器中打开文件。如果您使用保持文件句柄打开的编辑器(记事本不会),您会看到关闭文件而不保存不会导致 Get-Content -Wait 输出管道添加的行,因为您在编辑器中打开文件。

    • 制表符补全文件名

  • 在您尝试上述任何测试几次后,您会发现Get-Content -Wait 在管道执行的剩余部分定期输出更多行,即使您不执行任何操作。不是一次一行,而是分批。

  • 行为本身的不一致指向缓冲区刷新,这是根据难以预测的可变标准发生的,而不是在清晰一致的情况下发生的关闭。

结论: Get-Content -Wait 与宣传的完全一样。新内容在以物理方式写入磁盘* 上的文件后立即显示。

应该注意的是,我在驱动器上禁用写入缓存的建议对于上面的测试没有不是,即它没有导致 `Get-Content -Wait 在添加新行后立即显示通过管道传输到文本文件,因此可能导致输出延迟的缓冲发生在文件系统或操作系统级别,而不是磁盘的写缓存。但是,写缓冲显然是对 OP 问题中观察到的行为的解释。

* 我不打算详细讨论这个问题,因为它超出了问题的范围,但是如果您不在文件末尾添加内容,Get-Content -Wait 的行为确实很奇怪。它显示文件末尾的数据,其大小等于添加的数据量。新显示的数据一般会重复之前显示的数据,可能包含也可能不包含任何新数据,具体取决于新数据的大小是否超过其后数据的大小。

【讨论】:

  • log4net 确实在使用默认值,每次追加后刷新。尝试取消选中“在设备上启用写入缓存”但无法观察到任何差异,仍然 -wait 不会刷新(虽然没有重新启动机器)。如果我用 Notepad++ 打开日志文件,它确实会注意到发生了变化。我的问题似乎与 PS 访问文件的方式有关?
  • 投反对票,因为这是 Powershell 的问题。
  • @Duncan 那不正确。 Get-Content -Wait 完全按照文档中的描述工作。只要它监控的文件内容在磁盘上发生变化,它就会显示附加数据。它仅在文件关闭时显示更改的想法忽略了一些已经注意到的观察结果,但是在阅读了您的答案后,我做了一些进一步的测试,最终证明事实并非如此。我将在您的答案下添加 cmets 以更具体地解释。
  • 我很难相信这是一个写缓存问题。即使它 ,它也是 powershell 中一个令人难以置信的错误 - 除非 get-content 被明确记录为绕过标准文件访问并以某种方式进入原始文件系统,这将是非常奇怪的。我已经让这个运行了几个小时而没有显示任何更新。除非从根本上破坏写缓存,否则这不会发生。我猜测底层文件 is 正在更新,但无论 powershell 正在监控什么,实际上都是更高级别的东西,并且无论出于何种原因都不会发生。很奇怪。
  • ...经过一番挖掘,我猜这是文件的 Last Modified 日期 - 已发布详细信息作为答案
【解决方案6】:

我在尝试实时观看 WindowsUpdate.log 时遇到了同样的问题。虽然不理想,但下面的代码让我可以监控进度。 - 由于上述相同的文件写入限制,Wait 不起作用。

显示最后 10 行,休眠 10 秒,清除屏幕,然后再次显示最后 10 行。 CTRL + C 停止流。

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

【讨论】:

  • 感谢循环/睡眠语法 - 我对 PS 很陌生 :) 我用它来构建一个循环,在后台不断“接触”我的日志文件,然后 PS 将其拾取 - 不是确定某些是否适用于具有权限等的 WindowsUpdate.log,但如果它有用,请查看我编辑的答案
猜你喜欢
  • 1970-01-01
  • 2012-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-03
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
相关资源
最近更新 更多