【问题标题】:PowerShell - Overwriting line written with Write-HostPowerShell - 覆盖用 Write-Host 编写的行
【发布时间】:2014-09-15 11:20:05
【问题描述】:

我正在尝试覆盖用 Write-Host 编写的 PowerShell 中的一行(我有一个循环运行的进程,我想在屏幕上显示更新百分比)。我试图做的是:

Write-Host -NoNewline "`rWriting $outputFileName ($i/$fileCount)... $perc%"

但不是覆盖该行,而是停留在同一行并附加到该行。

我在这里错过了什么?

谢谢

【问题讨论】:

  • 只是一个建议,使用Write-Progress 可能会做一些类似于你想要实现的事情。
  • 奇怪。这对我来说没问题...
  • 将输出包装在 $(...) 中,所以它是 Write-Host -NoNewline $("`rWriting $outputFileName ($i/$fileCount)... $perc%")
  • 如您所见,有很多 pletora 方法可以做同样的事情或做类似的事情(并不总是可取的)。你没有遗漏任何东西,因为你的代码是正确的。它适用于 PowerShell 5 控制台;可能是当时的版本,或者它在 ISE 上不起作用。

标签: powershell


【解决方案1】:

你不能覆盖 Powershell 窗口中的一行。 你可以做的是用cls(Clear-Host) 使窗口空白:

# loop code
cls
Write-Host "`rWriting $outputFileName ($i/$fileCount)... $perc%"
# end loop

但您真正应该使用的是Write-Progress,这是一个专门为此目的而构建的 cmdlet:

# loop code
Write-Progress -Activity "Writing $outputFileName" -PercentComplete $perc
# end loop

更多关于Write-Progress这里:http://technet.microsoft.com/en-us/library/hh849902.aspx

【讨论】:

  • 这并不是我在写这篇文章时所寻找的。但我确实采纳了你的答案,因为(1)我不知道那个 cmdlt - 所以我学到了一些新东西并喜欢它。 (2) 它确实适合我的需要。不知道我最初正在寻找的东西是否可能(根据我读过的内容应该是) - 但就我而言,这是我选择的答案:)
  • 这正是我想要的。谢谢!通过搜索“powershell 覆盖输出行”找到。
  • Write-Host 因为 `r 而起作用,但为什么要使用 cls 呢?当程序/脚本可以简单地将其附加到下一行时,清除您的 shell 以显示进度是非常不好的。如果没有cls,会发生什么。因此,最好听第二部分并使用Write-Progress,如果你能容忍它在屏幕上的侵入性有多大
【解决方案2】:

作为对Raf's answer above 的调整,您不必每次都擦拭屏幕来更新最后一行。 用-NoNewLine 和回车`r 调用Write-Host 就足够了。

for ($a=0; $a -le 100; $a++) {
  Write-Host -NoNewLine "`r$a% complete"
  Start-Sleep -Milliseconds 10
}
Write-Host #ends the line after loop

【讨论】:

  • 是的 ISE 控制台行为与预期不同,但它适用于 powershell 控制台。
  • 这个答案确实是问题的正确答案。
【解决方案3】:

它并不完美,但这里有一个带有旋转字符的脚本。让你这样做的部分是:

$origpos = $host.UI.RawUI.CursorPosition
$origpos.Y += 1

获取当前位置并将其保存,以便我们可以继续引用它。随着您的进步,您可以更改$host.UI.RawUI.CursorPosition。由于之前已保存,您可以将其重置回$host.UI.RawUI.CursorPosition = $origpos。你应该可以尝试一下。

$scroll = "/-\|/-\|"
$idx = 0
$job = Invoke-Command -ComputerName $env:ComputerName -ScriptBlock { Start-Sleep -Seconds 10 } -AsJob

$origpos = $host.UI.RawUI.CursorPosition
$origpos.Y += 1

while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
    $host.UI.RawUI.CursorPosition = $origpos
    Write-Host $scroll[$idx] -NoNewline
    $idx++
    if ($idx -ge $scroll.Length)
    {
        $idx = 0
    }
    Start-Sleep -Milliseconds 100
}
# It's over - clear the activity indicator.
$host.UI.RawUI.CursorPosition = $origpos
Write-Host 'Complete'

Remove-Variable('job')


$job = Start-Job -ScriptBlock { Start-Sleep -Seconds 10 }
while (($job.State -eq "Running") -and ($job.State -ne "NotStarted"))
{
    Write-Host '.' -NoNewline
    Start-Sleep -Seconds 1
}
Write-Host ""

因此,只要您记住要返回的位置,就可以使用此逻辑。这在 ISE 中无法正常工作。您也可以使用 `b 作为退格字符。

【讨论】:

    【解决方案4】:

    我知道,那已经很老了,但我处于同样的情况并修改了 Boluwade Kujero 的解决方案,只是因为在写入新输出之前写入空行可能会导致“闪烁”输出。

    所以在下面的函数中,我只是覆盖现有的行,写空白直到到达旧的光标位置,然后回到新行的最后一个字符。

    此外,我添加了一个光学进度条。进度由函数通过给定参数计算:

    function Write-Status 
    {
      param([int]$Current,
            [int]$Total,
            [string]$Statustext,
            [string]$CurStatusText,
            [int]$ProgressbarLength = 35)
    
      # Save current Cursorposition for later
      [int]$XOrg = $host.UI.RawUI.CursorPosition.X
    
      # Create Progressbar
      [string]$progressbar = ""
      for ($i = 0 ; $i -lt $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0)); $i++) {
        $progressbar = $progressbar + $([char]9608)
      }
      for ($i = 0 ; $i -lt ($ProgressbarLength - $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0))); $i++) {
        $progressbar = $progressbar + $([char]9617)
      }
      # Overwrite Current Line with the current Status
      Write-Host -NoNewline "`r$Statustext $progressbar [$($Current.ToString("#,###").PadLeft($Total.ToString("#,###").Length)) / $($Total.ToString("#,###"))] ($($( ($Current / $Total) * 100).ToString("##0.00").PadLeft(6)) %) $CurStatusText"
    
      # There might be old Text behing the current Currsor, so let's write some blanks to the Position of $XOrg
      [int]$XNow = $host.UI.RawUI.CursorPosition.X
      for ([int]$i = $XNow; $i -lt $XOrg; $i++) {
        Write-Host -NoNewline " "
      }
      # Just for optical reasons: Go back to the last Position of current Line
      for ([int]$i = $XNow; $i -lt $XOrg; $i++) {
        Write-Host -NoNewline "`b"
      }
    }
    

    像这样使用函数:

    For ([int]$i=0; $i -le 8192; $i++) {
        Write-Status -Current $i -Total 8192 -Statustext "Running a long Task" -CurStatusText "Working on Position $i"
    }
    

    结果将是一个正在运行的进度条,如下所示(单行):

    运行一个长任务 ██████████████████░░░░░░░░░░░░░░░░░ [4.242 / 8.192] ( 51,78 %) 在 4242 位置工作

    希望这对其他人有帮助

    【讨论】:

    • 这不只是Write-Progress的复杂版本吗?
    【解决方案5】:

    您可以使用 .NET 控制台类在您想要的地方做您想做的事。 仅适用于控制台窗口,不适用于 ISE。

    cls
    [Console]::SetCursorPosition(40,5)
    [Console]::Write('Value of $i = ')
    [Console]::SetCursorPosition(40,7)
    [Console]::Write('Value of $j = ')
    For ($i = 1; $i -lt 11; $i++)
    {
        [Console]::SetCursorPosition(57,5)
        [Console]::Write($i)
        for ($j = 1; $j -lt 11; $j++)
        {
            [Console]::SetCursorPosition(57,7)
            [Console]::Write("$j ")  
            Start-Sleep -Milliseconds 200 
        }
        Start-Sleep -Milliseconds 200
    }
    
    [Console]::SetCursorPosition(40,5)
    [Console]::Write("                    `n")
    [Console]::SetCursorPosition(40,7)
    [Console]::Write("                    `n")
    
    [Console]::SetCursorPosition(0,0)
    

    【讨论】:

      【解决方案6】:

      如果目标是严格覆盖 powershell 控制台提示行(带有光标的当前行),那么这里的所有答案都只能在一定程度上起作用,并且在某些方面做得比预期的要多。

      Raf 和 Craig 在第一行使用 Clear-Host cmdlet (cls) 的答案,就像 Dullson 指出的那样,做得太多了。空白整个屏幕假定清除的内容对于查看不再重要,这可能不是真的。有时这些是理解当前行所必需的。

      Raf 的 Write-Progress 解决方案是一个功能强大的 cmdlet,但对于仅覆盖当前行似乎有点过头了。

      Raf 的 Write-Host 提案、Matt 的提交和 Dullson 的调整都很好,在特定屏幕位置上只有一个字符位置需要更新或后续行文本的长度比当前文本长的情况下。如果不是,则后续行文本只会覆盖当前行的长度范围,而后续行中长度位置比新行长的部分与新行一起保留在视图中。

      例如,如果之前的值为 10,而新的值为 9,则显示为 90。9 只是覆盖了之前值中等于其长度 - 1 的部分。因此,解决方案适用于增量,但对于值长度比以前减少的减量来说不太好。

      下面的代码块展示了如何保证当前行文本被新的文本完全(视觉)覆盖。

      $LongString = "This string is long"
      $ShortString = "This is short"
      
      #Simulate typing a string on the console line
      $L = 1
      While ($L -le $LongString.Length)
      {
          $Sub = $LongString.Substring(0,$L)
          Write-Host "`r$Sub" -NoNewline
          $L++
          # This sleep is just to simulate manual typing delay
          Start-Sleep -Milliseconds 20
      }
      
      # Now blank out the entire line with the space character " "
      # The quantity of spaces should be equal to the length of the current text
      # Which in this case is contained in $Sub.Length
      
      $Blank = " "
      For($L = 1; $L -le $Sub.Length; $L++)
          {            
              $Blank = $Blank + " "
          }
      Write-Host "`r$Blank" -NoNewline
      
      # Overwrite the blank console line with the new string    
      $L = 1
      While ($L -le $ShortString.Length)
      {
          $Sub = $ShortString.Substring(0,$L)
          Write-Host "`r$Sub" -NoNewline
          $L++
          # This sleep is just to simulate delay in manual typing
          Start-Sleep -Milliseconds 20
      }
      
      # The following is not required if you want the Powershell prompt
      # to resume to the next line and not overwrite current console line.
      # It is only required if you want the Powershell prompt to return
      # to the current console line.
      # You therefore blank out the entire line with spaces again.
      # Otherwise prompt text might be written into just the left part of the last
      # console line text instead of over its entirety.
      
      For($L = 1; $L -le $Sub.Length; $L++)
          {            
              $Blank = $Blank + " "
          }
      Write-Host "`r$Blank" -NoNewline
      Write-Host "`r" -NoNewline
      

      【讨论】:

      • 现在才看到这个。我的解决方案是基于保存以前的位置。只要你四处走动,你就可以使用相同的逻辑来编辑不同位置的多个字符。使用退格字符还可以让您删除尽可能多的内容。它不依赖于单个字符。它就是这样呈现的。
      • 如果我不知道当前显示的行的长度,并且想在写入之前用空格覆盖它 - 有没有办法找出当前显示的行的长度安慰?还是读取当前光标位置的字符?
      【解决方案7】:

      这是我从 Thomas Rayner 的博客文章中得到的。他使用ANSI Escape Sequences保存光标位置[s并更新光标位置[u

      $E=[char]27
      

      然后使用保存转义序列保存当前光标位置:

      "${E}[s"
      

      用法:使用更新序列${E}[u告诉PS从哪里开始字符串:

      1..10 | %{"${E}[uThere are $_ s remaining"; Start-Sleep -Seconds 1}
      

      但在 ISE 中不起作用。

      我知道链接过时了,但今天是here

      【讨论】:

      • 很高兴看到'旧' DOS ANSI.sys ESC/sequences 重新恢复运行......
      • 很好(请注意,该链接现在会导致无效证书警告)。顺便说一句,现在 PowerShell 是跨平台的:在类 Unix 平台上,不幸的是,这不能从 PowerShell 脚本中按预期工作(但在 macOS 默认终端中,它可以交互式工作,奇怪)。
      【解决方案8】:

      https://241931348f64b1d1.wordpress.com/2017/08/23/how-to-write-on-the-same-line-with-write-output/

      此方法对我有用,可以在循环中写入输出值,直到其状态更改为“成功”。确保将光标设置为所需的行数,它会覆盖同一行

      while($val -ne 1)
          {
          if($taskstates.Tasks.state[0] -eq "Succeeded" -and $taskstates.Tasks.state[1] -eq "Succeeded" -and $taskstates.Tasks.state[2] -eq "Succeeded" -and $taskstates.Tasks.state[3] -eq "Succeeded")
              {
                  $val = 1
              }
          #Clear-Host
          $taskstates.Tasks.StartTime[0].ToString() +" "+ $taskstates.Tasks.name[0] +" is "+ $taskstates.Tasks.state[0]
          $taskstates.Tasks.StartTime[1].ToString() +" "+ $taskstates.Tasks.name[1] +" is "+ $taskstates.Tasks.state[1]
          $taskstates.Tasks.StartTime[2].ToString() +" "+ $taskstates.Tasks.name[2] +" is "+ $taskstates.Tasks.state[2]
          $taskstates.Tasks.StartTime[3].ToString() +" "+ $taskstates.Tasks.name[3] +" is "+ $taskstates.Tasks.state[3]
          $taskstates = Get-ASRJob -Name $failoverjob.Name
          "ASR VMs build is in Progress"
          Start-Sleep 5
          [console]::setcursorposition($([console]::Cursorleft ),$([console]::CursorTop - 4))
          }
      

      【讨论】:

        【解决方案9】:

        我迟到了。这是我最近发现并适应我的目的的概念证明。此示例覆盖该行。

        $count = 1
        
        # Used for calculating the max number length for padding trailing spaces
        $totalCount = 100
        
        #Get current cursor position
        $curCursorPos = New-Object System.Management.Automation.Host.Coordinates
        $curCursorPos.X = $host.ui.rawui.CursorPosition.X
        $curCursorPos.Y = $host.ui.rawui.CursorPosition.Y
        
        # Counter code
        While ($count -le 100) {
        
        
            # Keep cursor in the same position on the same line
            $host.ui.rawui.CursorPosition = $curCursorPos
        
            # Display with padded trailing spaces to overwrite any extra digits
            $pad = ($totalCount -as [string]).Length
        
            # Display the counter
            Write-Host "$(([string]$count).Padright($pad))" -NoNewline -ForegroundColor Green
        
            # Run through the example quickly
            Start-Sleep -Milliseconds 100
        
            #increment $count
            $count++
        
        }
        

        您可以试验Write-Host -NoNewline 属性,保留或删除它,看看哪个更适合您。

        【讨论】:

          【解决方案10】:

          试试

          for ($i=1;$i -le 100;$i++){Write-Host -NoNewline "`r" $i;sleep 1}
          

          【讨论】:

          • 尝试添加一些关于您的解决方案的解释:)
          • "`r" 通常是\r 的powershell - 回车。这只是将光标返回到行首,因此文本被覆盖。我认为其他一切都清楚
          【解决方案11】:

          我喜欢下面的代码...

              $dots = ""
              while (!$isTrue) {
                  if ($dots -eq "...") {
                      $dots = ""
                  }
                  else {
                      $dots += "."
                  }
                  Write-Host -NoNewLine "`rLoading$dots"
                  Start-Sleep 1
              }
          

          【讨论】:

            【解决方案12】:

            您可以使用 $Host.UI.RawUI.WindowSize.Width 找到显示宽度,然后使用 .PadRight 用空格填充该行。这避免了每次循环都必须清除屏幕,字符问题从最后一个循环中持续存在,必须操纵光标位置,或者必须编写自定义函数或大量繁琐的代码,例如:

            # only works in a console window
            If ($Host.Name -eq "ConsoleHost")
            {
                Write-Host 'Starting...'
                
                # find the max line length of the console host
                $maxLineLength = $Host.UI.RawUI.WindowSize.Width
            
                # loop a few times
                For ($i = 1; $i -le 10; $i++)
                {
                    # for the sake of demonstration, generate a random-length string of letters
                    $randStringLength = Get-Random -Minimum 1 -Maximum $maxLineLength
                    $randCharIndex = Get-Random -Minimum 65 -Maximum (65+26) # A = ASCII 65
                    $randChar = ([char]$randCharIndex)
                    $myString = [string]$randChar*$randStringLength
            
                    # overwrite at the current console line
                    Write-Host ("`r"+$myString.PadRight($maxLineLength," ")) -NoNewline
            
                    # pause briefly before going again
                    Start-Sleep -Milliseconds 200
                }
            
                Write-Host 'Done.'
            }
            

            PowerShell 7.2+ 中的另一个选项是使用最小写入进度视图$PSStyle.Progress.View = Minimal

            # only works in a console window
            If ($Host.Name -eq "ConsoleHost")
            {
                # loop a few times
                For ($i = 1; $i -le 10; $i++)
                {
                    # for the sake of demonstration, generate a random-length string of letters
                    $randStringLength = Get-Random -Minimum 1 -Maximum 500
                    $randCharIndex = Get-Random -Minimum 65 -Maximum (65+26) # A = ASCII 65
                    $randChar = ([char]$randCharIndex)
                    $myString = [string]$randChar*$randStringLength
            
                    # overwrite at the current console line
                    Write-Progress -Activity $i -Status $myString
            
                    # pause briefly before going again
                    Start-Sleep -Milliseconds 200
                }
            }
            

            【讨论】:

            • 最干净的方法。
            • 在 PS 5.0 中,这需要在循环之后有一个空的 Write-Host,否则来自“完成”的第一个字母 (“D”)。不打印字符串。此外,主要的 Write-Host 命令看起来略有不同(不能按预期使用“+”):Write-Host "`r$str".PadRight($Host.UI.RawUI.WindowSize.Width, ' ') -NoNewline
            • @o.v 当我完全按照 PowerShell 5 或 7 控制台窗口中给出的代码运行代码时,一切都按预期工作,没有丢失字母。我注意到您使用的变量 $str 不在我的代码示例中,所以您提到的更改可能只是您的特定实现所必需的......?
            【解决方案13】:

            这里有很多好的建议...

            我使用 WindowTitle 栏来监控我的脚本的状态,指出我在我的代码中的位置,以及当前的进度。

            For($t = 0; $t -le 100; $t++) {
            $Host.UI.RawUI.WindowTitle = "进度 - $t% 完成"
            开始睡眠 - 毫秒 10
            }

            我什至会在我的代码中插入更新的“位置”信息,以指示我在代码中的位置:

            $Host.UI.RawUI.WindowTitle = "查询索引..."
            $Host.UI.RawUI.WindowTitle = "正在更新搜索字段..."
            $Host.UI.RawUI.WindowTitle = "正在执行 Robocopy..."

            当然还有当它完成时:

            $Host.UI.RawUI.WindowTitle = "脚本完成。"

            【讨论】:

              猜你喜欢
              • 2023-01-25
              • 1970-01-01
              • 1970-01-01
              • 2023-02-03
              • 1970-01-01
              • 2015-03-27
              • 2015-05-19
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多