PowerShell - 覆盖用Write-Host编写的行

时间:2014-09-15 11:20:06

标签: powershell

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

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

但不是覆盖线,而是保持在同一条线上并附加到它上面。

我在这里错过了什么?

由于

11 个答案:

答案 0 :(得分:17)

您无法覆盖Powershell窗口中的一行。您可以执行的操作是使用cls(清除主机)清空窗口:

# 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 :(得分:12)

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

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

答案 2 :(得分:9)

这不完美但是这里有一个旋转角色的脚本。允许您这样做的部分是:

$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作为后退空格字符。

答案 3 :(得分: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)

答案 4 :(得分:4)

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

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

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

Raf的写主机提案,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

答案 5 :(得分:3)

我知道,那已经很老了,但我处于相同的情况并修改了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

希望这会有助于其他人

答案 6 :(得分:1)

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

$E=[char]27

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

"${E}[s"

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

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

然而,在ISE中不起作用。

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

答案 7 :(得分:1)

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))
    }

答案 8 :(得分:1)

尝试

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

答案 9 :(得分:0)

我迟到了。这是我最近发现并针对我的目的进行了调整的概念证明。此示例覆盖了这一行。

$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 :(得分:0)

您可以使用$ 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.'
}