【问题标题】:Is there any way to colour text previously written using "Write-Host" in powershell?有什么方法可以为以前在 powershell 中使用 \"Write-Host\" 编写的文本着色?
【发布时间】:2023-02-03 03:41:18
【问题描述】:

我想创建“Select-Multiple”功能。

该函数有一些参数,但最重要的参数是选项列表。 比方说 @("First Option", "Second Option")

然后该函数将显示如下内容:

全部
b 第一个选项
c 第二个选项
d 退出
选择您的选项: > ...

“选择您的选项:> ...”文本将重复出现,只要:

  1. 用户选择“全部”或“退出”选项
  2. 用户将选择所有可能的选项(“全部”和“退出”除外)

    最后,该函数返回用户选择的选项列表。

    简单的。但是......我想强调用户已经选择的选项。 因此,如果用户选择“b”,则“b First Option”会变成绿色。

    是否可以在不使用 Clear-Host 的情况下执行类似的操作,因为我不想清除之前的步骤?

    我在 powershell 中附上了我的“Select-Multiple”功能,如果写得不好,我很抱歉,但我不经常使用 powershell。

    function Select-Multiple {
        Param(
            [Parameter(Mandatory=$false)]
            [string] $title,
            [Parameter(Mandatory=$false)]
            [string] $description,
            [Parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            $options,
            [Parameter(Mandatory=$true)]
            [string] $question
        )   
        if ($title) {
            Write-Host -ForegroundColor Yellow $title
            Write-Host -ForegroundColor Yellow ("-"*$title.Length)
        }
    
        if ($description) {
            Write-Host $description
            Write-Host
        }
    
        $chosen = @()
        $values = @()
        $offset = 0
    
        $all = "All"
        $values += @($all)
        Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
        Write-Host $all
        $offset++
        
        $options.GetEnumerator() | ForEach-Object {
            Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
            $values += @($_)
            Write-Host $_
            $offset++ 
        }
    
        $exit = "Exit"
        $values += @($exit)
        Write-Host -ForegroundColor Yellow "$([char]($offset+97)) " -NoNewline
        Write-Host $exit
    
        $answer = -1
        while($chosen.Count -ne $options.Count) {
            Write-Host "$question " -NoNewline
            $selection = (Read-Host).ToLowerInvariant()
            if (($selection.Length -ne 1) -or (([int][char]($selection)) -lt 97 -or ([int][char]($selection)) -gt (97+$offset))) {
                Write-Host -ForegroundColor Red "Illegal answer. " -NoNewline
            }
            else {
                $answer = ([int][char]($selection))-97
                $value = $($values)[$answer]
                if ($value -eq $exit) {
                    return $chosen
                }
    
                if ($value -eq $all) {
                    return $options
                }
                else {
                    if ($chosen.Contains($value)) {
                        Write-Host -ForegroundColor Red "The value $value was already chosen."
                    }
                    else {
                        $chosen += ($value)
                    }
                }
            }
            if ($answer -eq -1) {
                Write-Host -ForegroundColor Red "Please answer one letter, from a to $([char]($offset+97))"
            }
            $answer = -1;
        }
        return $chosen
    }
    

【问题讨论】:

  • 我不相信这可以使用写主机来完成。你可以有一个输出链,每个新的输出都可以有亮点,或者你可以按照你的建议做,清除主机,然后只做一个新的写入主机。

标签: powershell


【解决方案1】:

由于控制台窗口的工作方式,您不能只为现有行重新着色。向控制台写入内容后,唯一可以修改它的方法就是覆盖它。这在应用颜色时没有什么不同。

要理解为什么会这样,让我们​​回顾一下 PowerShell 中文本的着色方式。让我们以下面的命令为例:

Write-Host "Test" -ForegroundColor Green

这是此命令将执行的操作的(简化的)分步概述:

  1. 将控制台的ForegroundColor属性设置为绿色
  2. 将“测试”写入控制台
  3. 将控制台的 ForegroundColor 属性设置为之前的任何值

    这解释了为什么我们无法更改已写入控制台的文本的颜色。如果要给一段文字上色,需要设置控制台颜色将文本写入控制台。

    但是,有几种方法可以创建相同的视觉效果。实际上恰好有两种方法。其中之一是清除控制台并重写您提到的您不想做的所有事情,所以让我们先谈谈另一种方式。


    覆盖单行

    首先让我说这在 PowerShell ISE 上效果不佳。如果您决定使用它,则必须使用普通的 PowerShell 控制台或支持它的 IDE 来调试它。这也是更复杂的选项,所以如果你不想处理它的复杂性,第二个选项就是要走的路。

    控制台窗口允许您使用Console.CursorLeftConsole.CursorTop 检索和设置光标位置。如果你需要同时设置它们,你也可以使用Console.SetCursorPosition()。还有$Host.UI.RawUI.CursorPosition,但是它很长,而且和Read-Host搭配使用会有一些奇怪的副作用,所以我不推荐使用它。当您将输出写入控制台时,它会将输出写入光标恰好所在的位置。我们可以通过将光标位置设置为我们要更改颜色的行的开头来利用它,然后用彩色文本覆盖普通文本,反之亦然。

    为了做到这一点,我们需要做的就是跟踪哪个选项在哪一行。这非常简单,特别是如果您有一组选项,这些选项的顺序与您将它们打印到控制台的顺序相同。

    这是我制作的一个简单脚本,它正是这样做的:

    $options = $("Option 1", "Option 2", "Option 3")
    $initialCursorTop = [Console]::CursorTop
    
    # An array to keep track of which options are selected.
    # All entries are initially set to $false.
    $selectedOptionArr = New-Object bool[] $options.Length
    
    for($i = 0; $i -lt $options.Length; $i++)
    {
        Write-Host "$($i + 1). $($options[$i])"
    }
    Write-Host # Add an extra line break to make it look pretty
    
    while($true)
    {
        Write-Host "Choose an option>" -NoNewline
        $input = Read-Host
        $number = $input -as [int]
        if($number -ne $null -and 
           $number -le $options.Length -and 
           $number -gt 0)
        {
            # Input is a valid number that corresponds to an option.
    
            $oldCursorTop = [Console]::CursorTop
            $oldCursorLeft = [Console]::CursorLeft
    
            # Set the cursor to the beginning of the line corresponding to the selected option.
            $index = $number - 1
            [Console]::SetCursorPosition(0, $index + $initialCursorTop)
            $choice = $options[$index]
            $isSelected = $selectedOptionArr[$index]
    
            $choiceText = "$($number). $($choice)"
            if($isSelected)
            {
                Write-Host $choiceText -NoNewline
            }
            else
            {
                Write-Host $choiceText -ForegroundColor Green -NoNewline
            }
            $selectedOptionArr[$index] = !$isSelected
            
    
            [Console]::SetCursorPosition($oldCursorLeft, $oldCursorTop)
        }
    
        # Subtract 1 from Y to compensate for the new line created when providing input.
        [Console]::SetCursorPosition(0, [Console]::CursorTop - 1)
    
        # Clear the input line.
        Write-Host (' ' * $Host.UI.RawUI.WindowSize.Width) -NoNewline
        [Console]::CursorLeft = 0
    }
    

    这种方法的主要优点是它不需要清除整个控制台来更新文本。这意味着您可以在其上方显示任何您想要的内容,而不必担心每次用户输入内容时它都会被清除。另一个优点是这执行最少数量的操作以执行任务。

    主要缺点是它相对不稳定。这要求您使用准确的行号,因此如果发生某些行偏移的情况(例如一个选项是多行),很可能会导致一些重大问题。

    然而,这些缺点是可以克服的。由于您可以访问 $Host.UI.RawUI.WindowSize.Width,它告诉您可以在一行中放置多少个字符,我们知道任何长度大于此长度的字符串都将换行到多行。另一种选择是只跟踪光标从哪一行开始,然后您可以清除起始位置和光标当前所在位置之间的所有文本。


    清除控制台

    这种方法要简单得多,因为您根本不必担心哪一行或光标所在的位置。这个想法是您只需清除整个控制台,然后用您想要进行的更改重写所有内容。这是核方法,但也是最可靠的方法。

    这是使用此方法的与上面相同的示例:

    $options = $("Option 1", "Option 2", "Option 3")
    
    # An array to keep track of which options are selected.
    # All entries are initially set to $false.
    $selectedOptionArr = New-Object bool[] $options.Length
    
    while($true)
    {
        Clear-Host
    
        for($i = 0; $i -lt $options.Length; $i++)
        {
            if($selectedOptionArr[$i])
            {
                Write-Host "$($i + 1). $($options[$i])" -ForegroundColor Green
            }
            else
            {
                Write-Host "$($i + 1). $($options[$i])"
            }
        }
        Write-Host # Add an extra line break to make it look pretty
        Write-Host "Choose an option>" -NoNewline
    
        $input = Read-Host
        $number = $input -as [int]
    
        if($number -ne $null -and 
           $number -le $options.Length -and 
           $number -gt 0)
        {
            # Input is a valid number that corresponds to an option.
    
            $index = $number - 1
            $choice = $options[$index]
            $selectedOptionArr[$index] = !$selectedOptionArr[$index]
        }
    }
    

    这种方法的主要优点是超级简单易懂。

    主要缺点是每次用户输入内容时都会清除整个控制台。在大多数情况下,这不是一个大问题,但它可能适用于大型数据集。

【讨论】:

    【解决方案2】:

    我同意 Trevor Winge 的观点,我很可能无法更改较早的控制台输出外观,但这肯定不值得您花时间。控制台有某些限制,这没关系,因为这就是 GUI 的用途。我希望您对这种情况感到鼓舞,去研究 Windows.Froms 或 WPF。使用控件!复选框对您的场景很有趣。我知道这并不完全是您所要求的,但它保证是一次值得您花时间的旅程。当我用 powershell 开始我的第一个 GUI 时,我很惊讶我能用很少的钱完成这么多。 Stackoverflow 充满了例子。

    【讨论】:

      猜你喜欢
      • 2015-03-27
      • 1970-01-01
      • 1970-01-01
      • 2011-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-18
      相关资源
      最近更新 更多