You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PowerShell中如何为已选选项着色且不使用Clear-Host?

Select-Multiple 函数优化:高亮选中项且不清除屏幕

需求说明

我需要实现一个Select-Multiple函数,核心功能和要求如下:

  • 接收选项列表(例如@("First Option", "Second Option"))、标题、描述、提问文本等参数
  • 生成带字母标识的交互菜单,示例格式:
    a All
    b First Option
    c Second Option
    d Exit
    Choose your option: > ...
    
  • 持续显示提问文本,直到满足任一终止条件:
    1. 用户选择All或Exit选项
    2. 用户选中所有除All和Exit外的可选选项
  • 最终返回用户选中的选项列表
  • 核心要求:不使用Clear-Host命令,实现已选中选项的高亮显示(例如选中b后,b First Option变为绿色)

初始版本代码

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
}

优化后的实现

function Select-Multiple {
    Param(
        [Parameter(Mandatory=$false)]
        [string] $title,
        [Parameter(Mandatory=$false)]
        [string] $description,
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        $options,
        [Parameter(Mandatory=$true)]
        [string] $question
    )   

    # 计算菜单总行数,用于光标回退定位
    $baseMenuLines = 1  # All选项行
    $baseMenuLines += $options.Count  # 用户自定义选项行数
    $baseMenuLines += 1  # Exit选项行
    if ($title) { $baseMenuLines += 2 }  # 标题行+分隔线行
    if ($description) { $baseMenuLines += 2 }  # 描述行+空行

    $chosen = @()
    $renderedOnce = $false

    # 定义菜单渲染函数,负责绘制带高亮的菜单
    function Render-Menu {
        if ($script:renderedOnce) {
            # 使用ANSI转义序列将光标向上移动到菜单起始位置,覆盖重绘
            Write-Host "`e[$($baseMenuLines)A" -NoNewline
        }
        else {
            $script:renderedOnce = $true
        }

        # 渲染标题
        if ($title) {
            Write-Host -ForegroundColor Yellow $title
            Write-Host -ForegroundColor Yellow ("-"*$title.Length)
        }

        # 渲染描述
        if ($description) {
            Write-Host $description
            Write-Host
        }

        # 渲染All选项
        $all = "All"
        Write-Host -ForegroundColor Yellow "a " -NoNewline
        Write-Host $all

        # 渲染用户自定义选项,高亮已选中项
        $offset = 1
        $options.GetEnumerator() | ForEach-Object {
            $char = [char]($offset + 97)
            Write-Host -ForegroundColor Yellow "$char " -NoNewline
            if ($chosen.Contains($_)) {
                Write-Host -ForegroundColor Green $_
            }
            else {
                Write-Host $_
            }
            $offset++ 
        }

        # 渲染Exit选项
        $exit = "Exit"
        $exitChar = [char]($offset + 97)
        Write-Host -ForegroundColor Yellow "$exitChar " -NoNewline
        Write-Host $exit
    }

    # 首次渲染菜单
    Render-Menu

    while($chosen.Count -ne $options.Count) {
        # 显示提问文本
        Write-Host "$question " -NoNewline
        $selection = (Read-Host).ToLowerInvariant()
        
        # 清除当前行的错误提示内容
        Write-Host "`e[K" -NoNewline

        $validInput = $true
        $maxCharCode = 97 + $options.Count + 1  # All + 选项 + Exit 的总数量
        if (($selection.Length -ne 1) -or (([int][char]$selection) -lt 97 -or ([int][char]$selection) -gt $maxCharCode)) {
            Write-Host -ForegroundColor Red "非法输入,请输入a到$([char]$maxCharCode)之间的单个字母"
            $validInput = $false
        }
        else {
            $answer = ([int][char]$selection) - 97
            $value = @("All") + $options + @("Exit") | Select-Object -Index $answer
            
            switch ($value) {
                "Exit" { return $chosen }
                "All" { return $options }
                default {
                    if ($chosen.Contains($value)) {
                        Write-Host -ForegroundColor Red "选项 '$value' 已被选中"
                    }
                    else {
                        $chosen += $value
                        # 重绘菜单以高亮新选中的选项
                        Render-Menu
                    }
                }
            }
        }

        # 输入无效时,回退到提问行位置,等待下一次输入
        if (-not $validInput) {
            Write-Host "`e[1A`e[K" -NoNewline
        }
    }
    return $chosen
}

关键优化点说明

  1. ANSI转义序列控制光标:
    • 使用 `e[$nA将光标向上移动指定行数,回到菜单起始位置,实现菜单的覆盖重绘,无需调用Clear-Host
    • 使用 `e[K清除当前行的所有内容,避免错误提示文本残留
  2. 提取菜单渲染逻辑:将菜单绘制逻辑封装为Render-Menu函数,每次选中新选项后调用该函数,动态高亮已选中的选项(绿色显示)
  3. 输入验证优化:调整字母范围计算逻辑,确保输入验证匹配菜单的实际选项数量
  4. 界面整洁处理:输入无效时,自动回退到提问行并清除错误提示,保持交互界面的整洁

内容的提问来源于stack exchange,提问作者Dreeze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 06:25:06