26

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

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

有谁知道为什么会这样?

4

6 回答 6

23

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

你说的很对。-Waiton 选项会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来停止命令,因此运行该命令 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显示新内容的触发器,但我敢肯定你误解了它。

于 2014-02-25T12:29:21.153 回答
11

看起来 Powershell 正在监视文件的Last Modified属性。问题是“出于性能原因”,包含此属性的 NTFS 元数据不会自动更新 ,除非在某些情况下。

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

您可以通过让 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
于 2015-04-27T19:33:05.753 回答
2

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

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

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

这在另一个会话中:

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

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

于 2013-11-12T02:21:43.463 回答
1

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

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

 while(1){
Get-Content C:\Windows\WindowsUpdate.log -tail 10 
    Start-Sleep -Seconds 10
    Clear 
    }
于 2015-04-27T20:20:13.617 回答
1

似乎 get-content 仅在通过 windows api 并且附加到文件的版本不同时才有效。

program.exe > output.txt

接着

get-content output.txt -wait

不会更新。但

program.exe | add-content output.txt

将与。

get-content output.txt -wait    

所以我想这取决于应用程序如何输出。

于 2014-02-08T16:39:42.227 回答
1

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

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

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

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


更新:

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

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

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

  • 我创建了一个 Excel 电子表格,并Get-Content -Wait针对 .xlsx 文件运行。当我在电子表格中输入新数据时,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% 一致的。有时,读取模式目录不会在第一次触发更多输出,但如果您重复操作就会触发。在触发更新输出的第一个重复之后的所有后续重复都无效。

      »  如果您重复测试,读取相同的属性不会触发输出,除非您在再次运行管道之前删除 .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如果你不在最后向文件中添加内容,它的行为就会很奇怪。它显示文件末尾的数据,其大小等于添加的数据量。新显示的数据通常会重复之前显示的数据,并且可能包含也可能不包含任何新数据,具体取决于新数据的大小是否超过其后的数据大小。

于 2013-11-12T06:40:35.707 回答