【问题标题】:Watch file for changes and run command with powershell监视文件的更改并使用 powershell 运行命令
【发布时间】:2015-05-17 23:19:52
【问题描述】:

是否有任何简单的方法(即脚本)在 Powershell 中监视文件并在文件更改时运行命令。我一直在谷歌搜索,但找不到简单的解决方案。基本上我在 Powershell 中运行脚本,如果文件更改,则 Powershell 运行其他命令。

编辑

好吧,我想我犯了一个错误。我不需要脚本,我可以将它包含在我的$PROFILE.ps1 文件中。但是,我一直在努力,但我仍然无法编写它,所以我会给予赏金。它必须看起来像这样:

function watch($command, $file) {
  if($file #changed) {
    #run $command
  }
}

有一个 NPM 模块正在做我想做的事,watch,但它只监视文件夹而不是文件,而且它不是 Powershell xD。

【问题讨论】:

  • 我已经扩展了我的答案,包括针对您的要求的解决方案。这就是你所追求的吗?

标签: powershell scripting cmd


【解决方案1】:

您可以使用System.IO.FileSystemWatcher 来监控文件。

$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $searchPath
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true

另见this article

【讨论】:

  • 好吧,我在谷歌上找到了,但我很难过如何在一个可以监视文件和触发命令的函数中实现它?
  • 看看这个reference
  • 嗯,我一直在读这篇文章,但我不知道该怎么做。我使用 powershell 已经有一段时间了,写了很多函数,但这太令人困惑了。
  • 如何获得观察者的输出?
  • 仅供参考 Jan 的回答要好得多。展示如何从观察者获取输出
【解决方案2】:

这是我在我的 sn-ps 中找到的一个示例。希望它更全面一点。

首先,您需要创建一个文件系统观察者,然后订阅观察者正在生成的事件。此示例侦听“创建”事件,但可以轻松修改以注意“更改”。

$folder = "C:\Users\LOCAL_~1\AppData\Local\Temp\3"
$filter = "*.LOG"
$Watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ 
    IncludeSubdirectories = $false
    NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onCreated = Register-ObjectEvent $Watcher -EventName Created -SourceIdentifier FileCreated -Action {
   $path = $Event.SourceEventArgs.FullPath
   $name = $Event.SourceEventArgs.Name
   $changeType = $Event.SourceEventArgs.ChangeType
   $timeStamp = $Event.TimeGenerated
   Write-Host "The file '$name' was $changeType at $timeStamp"
   Write-Host $path
   #Move-Item $path -Destination $destination -Force -Verbose
}

我会尽量缩小范围以满足您的要求。

如果您将其作为“profile.ps1”脚本的一部分运行,您应该阅读The Power of Profiles,其中解释了可用的不同配置文件脚本等等。

另外,您应该了解,等待文件夹中的更改不能作为脚本中的函数运行。必须完成配置文件脚本,您的 PowerShell 会话才能开始。但是,您可以使用函数来注册事件。

它的作用是注册一段代码,每次触发事件时执行。此代码将在会话保持打开时在当前 PowerShell 主机(或 shell)的上下文中执行。它可以与主机会话交互,但不知道注册代码的原始脚本。在您的代码被触发时,原始脚本可能已经完成。

代码如下:

Function Register-Watcher {
    param ($folder)
    $filter = "*.*" #all files
    $watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ 
        IncludeSubdirectories = $false
        EnableRaisingEvents = $true
    }

    $changeAction = [scriptblock]::Create('
        # This is the code which will be executed every time a file change is detected
        $path = $Event.SourceEventArgs.FullPath
        $name = $Event.SourceEventArgs.Name
        $changeType = $Event.SourceEventArgs.ChangeType
        $timeStamp = $Event.TimeGenerated
        Write-Host "The file $name was $changeType at $timeStamp"
    ')

    Register-ObjectEvent $Watcher -EventName "Changed" -Action $changeAction
}

 Register-Watcher "c:\temp"

运行此代码后,更改“C:\temp”目录(或您指定的任何其他目录)中的任何文件。您将看到触发代码执行的事件。

此外,您可以注册的有效 FileSystemWatcher 事件是“已更改”、“已创建”、“已删除”和“重命名”。

【讨论】:

  • 嗯,它应该在 shell 运行时阻塞 shell,就像每个 watch 函数一样。
  • 很公平,我一定有点误解了你的问题。也许您可以编辑并添加它作为要求。另外,您是在等待文件中的一次更改还是多次更改?
  • 很好的答案,@JanChrbolka 小问题,输出缺少字符串中的结束 "。(所以编辑需要 6 个字符)
  • @Crypth,谢谢你,我已经修正了错字。
  • @jpaugh..有没有办法在 PS 中创建不需要活动会话的观察者或事件?我已经使用您的代码开发了一个文件监视器,它可以完成我的大部分目标。最后一部分是让观察者在服务器上工作,如果自动化进程将文件放入文件夹中,它就会工作,无论我是否连接到服务器。
【解决方案3】:

我遇到了类似的问题。我最初想使用 Windows 事件和注册,但这将不像下面的解决方案那样容错。
我的解决方案是轮询脚本(间隔 3 秒)。该脚本在系统上的占用空间最小,并且可以非常快速地通知更改。在循环期间,我的脚本可以做更多的事情(实际上我检查了 3 个不同的文件夹)。

我的投票脚本是通过任务管理器启动的。计划每 5 分钟启动一次,并带有 stop-when-already-running 标志。这样,它会在重新启动或崩溃后重新启动。
使用任务管理器每 3 秒轮询一次对于任务管理器来说过于频繁。 当您将任务添加到调度程序时,请确保您不使用网络驱动器(这将需要额外的设置)并授予您的用户批处理权限。

我通过在午夜前几分钟将其关闭来让我的脚本重新开始。任务管理器每天早上启动脚本(我的脚本的init函数会在午夜左右退出1分钟)。

【讨论】:

  • 如何每 3 秒轮询一次更容错,然后订阅更改发生时生成的事件。取决于您的要求,但在 3 秒内您可能会错过数千个事件。
  • 在我的情况下,每分钟都有几个文件。当我的程序仍在启动但尚未侦听或重新启动 Windows 服务时到达的文件,我不在乎这些。当您采取预防措施并查看 blogs.msdn.com/b/winsdk/archive/2014/10/29/… 之类的东西时,使用事件也应该是可靠的。
  • 这很公平,我个人认为 FileSystemWatcher 事件非常可靠,因为我还没有遇到缓冲区限制。在我的代码中,我正在寻找只出现几分之一秒的小型临时文件。
【解决方案4】:
  1. 计算文件列表的哈希值
  2. 将其存储在字典中
  3. 按时间间隔检查每个哈希
  4. 当哈希值不同时执行操作

function watch($f, $command, $interval) {
    $sha1 = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider
    $hashfunction = '[System.BitConverter]::ToString($sha1.ComputeHash([System.IO.File]::ReadAllBytes($file)))'
    $files = @{}
    foreach ($file in $f) {
        $hash = iex $hashfunction
        $files[$file.Name] = $hash
        echo "$hash`t$($file.FullName)"
    }
    while ($true) {
        sleep $interval
        foreach ($file in $f) {
            $hash = iex $hashfunction
            if ($files[$file.Name] -ne $hash) {
                iex $command
            }
        }
    }
}

示例用法:

$c = 'send-mailmessage -to "admin@whatever.com" -from "watch@whatever.com" -subject "$($file.Name) has been altered!"'
$f = ls C:\MyFolder\aFile.jpg

watch $f $c 60

【讨论】:

    【解决方案5】:

    我将添加另一个答案,因为我之前的答案确实没有满足要求。

    要求

    • 编写一个函数来WAIT以等待特定文件的更改
    • 当检测到更改时,该函数将执行预定义的命令并将执行返回给主脚本
    • 文件路径和命令作为参数传递给函数

    已经有一个使用文件哈希的答案。我想按照我之前的回答向您展示如何使用 FileSystemWatcher 来完成。

    $File = "C:\temp\log.txt"
    $Action = 'Write-Output "The watched file was changed"'
    $global:FileChanged = $false
    
    function Wait-FileChange {
        param(
            [string]$File,
            [string]$Action
        )
        $FilePath = Split-Path $File -Parent
        $FileName = Split-Path $File -Leaf
        $ScriptBlock = [scriptblock]::Create($Action)
    
        $Watcher = New-Object IO.FileSystemWatcher $FilePath, $FileName -Property @{ 
            IncludeSubdirectories = $false
            EnableRaisingEvents = $true
        }
        $onChange = Register-ObjectEvent $Watcher Changed -Action {$global:FileChanged = $true}
    
        while ($global:FileChanged -eq $false){
            Start-Sleep -Milliseconds 100
        }
    
        & $ScriptBlock 
        Unregister-Event -SubscriptionId $onChange.Id
    }
    
    Wait-FileChange -File $File -Action $Action
    

    【讨论】:

      【解决方案6】:

      这是我根据之前的几个答案得出的解决方案。我特别想要:

      1. 我的代码是代码,而不是字符串
      2. 我的代码将在 I/O 线程上运行,以便我可以看到控制台输出
      3. 每次发生变化时都会调用我的代码,而不是一次

      旁注:由于讽刺的是使用全局变量在线程之间进行通信以便编译 Erlang 代码,因此我留下了我想要运行的细节。

      Function RunMyStuff {
          # this is the bit we want to happen when the file changes
          Clear-Host # remove previous console output
          & 'C:\Program Files\erl7.3\bin\erlc.exe' 'program.erl' # compile some erlang
          erl -noshell -s program start -s init stop # run the compiled erlang program:start()
      }
      
      Function Watch {    
          $global:FileChanged = $false # dirty... any better suggestions?
          $folder = "M:\dev\Erlang"
          $filter = "*.erl"
          $watcher = New-Object IO.FileSystemWatcher $folder, $filter -Property @{ 
              IncludeSubdirectories = $false 
              EnableRaisingEvents = $true
          }
      
          Register-ObjectEvent $Watcher "Changed" -Action {$global:FileChanged = $true} > $null
      
          while ($true){
              while ($global:FileChanged -eq $false){
                  # We need this to block the IO thread until there is something to run 
                  # so the script doesn't finish. If we call the action directly from 
                  # the event it won't be able to write to the console
                  Start-Sleep -Milliseconds 100
              }
      
              # a file has changed, run our stuff on the I/O thread so we can see the output
              RunMyStuff
      
              # reset and go again
              $global:FileChanged = $false
          }
      }
      
      RunMyStuff # run the action at the start so I can see the current output
      Watch
      

      如果你想要更通用的东西,你可以将文件夹/过滤器/操作传递给 watch。希望这对其他人来说是一个有用的起点。

      【讨论】:

        【解决方案7】:

        这是另一种选择。

        我只需要编写自己的代码来在 Docker 容器中观察和运行测试。 Jan 的解决方案要优雅得多,但 FileSystemWatcher 目前在 Docker 容器中已损坏。我的方法与 Vasili 的方法类似,但更懒惰,信任文件系统的写入时间。

        这是我需要的函数,它在每次文件更改时运行命令块。

        function watch($command, $file) {
            $this_time = (get-item $file).LastWriteTime
            $last_time = $this_time
            while($true) {
                if ($last_time -ne $this_time) {
                    $last_time = $this_time
                    invoke-command $command
                }
                sleep 1
                $this_time = (get-item $file).LastWriteTime
            }
        }
        

        这是一个等到文件更改,运行块,然后退出。

        function waitfor($command, $file) {
            $this_time = (get-item $file).LastWriteTime
            $last_time = $this_time
            while($last_time -eq $this_time) {
                sleep 1
                $this_time = (get-item $file).LastWriteTime
            }
            invoke-command $command
        }
        

        【讨论】:

          【解决方案8】:

          另一个简单的版本:

          $date = get-date
          while ( (dir file.txt -ea 0 | % lastwritetime) -lt $date -and $count++ -lt 10) {
            sleep 1
          }
          'file changed or timeout'
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-11-09
            • 1970-01-01
            • 1970-01-01
            • 2015-06-29
            • 1970-01-01
            • 2014-10-31
            相关资源
            最近更新 更多