批处理脚本:突出显示的select的导航菜单

我痴迷于纯粹的命令行模仿8位时代程序的function的想法。 所以我想做一个菜单选项,可以select标称(因为没有箭头键的箭头键)左右键和确认回车数组。 为了保持清楚,我写了一个工作的例子。 请注意ANSI转义码我使用了很多,不确定ESC控制字符是否会在浏览器中显示。

@ECHO OFF ECHO Chose and option with [36mZ[0m for left, [36mX[0m for right, [36mC[0m for confirm :PICKMENU SETLOCAL SET menumsg="[7mExit [0m Next" SET menumsgDef="[7mExit [0m Next" SET menumsgNxt="[0mExit [7m Next" :subpick CHOICE /N /C zxc /m:[3;1H%menumsg% IF %errorlevel% EQU 0 ECHO Just in case of errors & GOTO PICKMENU IF %errorlevel% EQU 1 SET menumsg=%menumsgDef% & GOTO subpick IF %errorlevel% EQU 2 SET menumsg=%menumsgNxt% & GOTO subpick IF %errorlevel% EQU 3 GOTO confirm :confirm IF %menumsg% EQU %menumsgNxt% (ECHO [0mNext option is chosen & GOTO PICKMENU) ELSE (ECHO ^Exit is chosen! & TIMEOUT 2 >NUL & EXIT) ENDLOCAL PAUSE 

我的问题开始时,我尝试包装在某种function,这里我可以传递参数,而不是每次我需要菜单或对话时,使hodgie代码包装此菜单:

 @ECHO OFF REM ----So I start with setting variables with idea of argument string tokenisation on the mind---- SET $teststring=OptionOne OptionTwo OptionThree SET /A $currentitem=1 REM ----I would use function that I found here on Stackoverflow to count amount of options in my string.---- REM ----This is magic for me, without this solution I would be using FOR loop and delims---- CALL :COUNTOPTIONS %$teststring% :COUNTOPTIONS SETLOCAL EnableDelayedExpansion SET /A Count=0 :repeatme IF NOT "%1"=="" ( SET /A Count+=1 SHIFT GOTO :repeatme ) ENDLOCAL & SET $optionsamount=%Count% ECHO The number of words is %$optionsamount% PAUSE :PICKCURRENT REM ----So I got maximum number of options, I guess it would be useful since by design user could stumble upon the REM ----edge of menu by occasionaly pressing "left" or "right" controls too much. Thats how I limit the variable... IF %$currentitem% LSS 1 SET /A $currentitem=1 IF %$currentitem% GTR %$optionsamount% SET /A $currentitem=%$optionsamount% ECHO %$optionsamount% REM ----This is where I completely lost it. I am trying to echo "options" string as separated items to the target location...----- FOR /F "tokens=*" %%A IN ("%$teststring%") DO ECHO [1;1H%%A REM ----...and echo "currently selected" item into the same place... FOR /F "tokens=%$currentitem%" %%B IN ("%$itemstring%") DO SET $selector=[1;1H[7m%%B[0m REM ----...and make it interactive with choice---- CHOICE /N /C zxc /m:%$selector% IF %errorlevel% EQU 0 ECHO oops & GOTO PICKITEM IF %errorlevel% EQU 1 SET /A "$currentitem=$currentitem-1" & GOTO PICKCURRENT IF %errorlevel% EQU 2 SET /A "$currentitem=$currentitem+1" & GOTO PICKCURRENT IF %errorlevel% EQU 3 GOTO confirm PAUSE 

(我铲脚本结构有利于说明我在做什么)。

显然,我的问题是缺乏理解的循环和function,但是对ss64的阅读槽参考和强烈的使用googlesearch并不是那么有启发性。 所以我希望指出教育资料以及对我的代码的评论。 我想强调的是,问题陈述并不意味着使用额外的库或资源工具包。

Solutions Collecting From Web of "批处理脚本:突出显示的select的导航菜单"

要求图书馆和教程的建议超出了SO的范围,而且你的代码太接近睡前的时间了。 也许我明天再看一遍。 我看到的最明显的问题是你的函数位于主运行时间,你永远不会超过它们。 他们应该在最后的goto :EOF或者exit /B这样它们不会在批处理脚本的正常的从上到下的顺序中执行。 例:

 @echo off & setlocal call :add 5 9 sum echo The sum is %sum% exit /b rem // Functions go here, below exit /b :add <num1> <num2> <return_var> set /a %~3=%~1 + %~2 goto :EOF :subtract <num1> <num2> <return_var> set /a %~3=%~1 - %~2 goto :EOF 

但我至少现在想,我可能会分享一个更完整的菜单脚本,不需要一个ansi解释器。

 <# : Batch portion @echo off & setlocal enabledelayedexpansion set "menu[0]=Format C:" set "menu[1]=Send spam to boss" set "menu[2]=Truncate database *" set "menu[3]=Randomize user password" set "menu[4]=Download Dilbert" set "menu[5]=Hack local AD" for /L %%I in (6,1,15) do set "menu[%%I]=loop-generated demo item %%I" set "default=0" powershell -noprofile "iex (${%~f0} | out-string)" echo You chose !menu[%ERRORLEVEL%]!. goto :EOF : end batch / begin PowerShell hybrid chimera #> $menutitle = "CHOOSE YOUR WEAPON" $menuprompt = "Use the arrow keys. Hit Enter to select." $menufgc = "yellow" $menubgc = "darkblue" [int]$selection = $env:default $h = $Host.UI.RawUI.WindowSize.Height $w = $Host.UI.RawUI.WindowSize.Width # assume the dialog must be at least as wide as the menu prompt $len = [math]::max($menuprompt.length, $menutitle.length) # get all environment vars matching menu[int] $menu = gci env: | ?{ $_.Name -match "^menu\[(\d+)\]$" } | sort @{ # sort on array index as int Expression={[int][RegEx]::Match($_.Name, '\d+').Value} } | %{ $val = $_.Value.trim() # truncate long values if ($val.length -gt ($w - 8)) { $val = $val.Substring(0,($w - 11)) + "..." } $val # as long as we're looping through all vals anyway, check whether the # dialog needs to be widened $len = [math]::max($val.Length, $len) } # dialog must accomodate string length + box borders + idx label $dialogwidth = $len + 8 # center horizontally $xpos = [math]::floor(($w - $dialogwidth) / 2) # center at top 1/3 of the console $ypos = [math]::floor(($h - ($menu.Length + 4)) / 3) # Is the console window scrolled? $offY = [console]::WindowTop # top left corner coords... $x = [math]::max(($xpos - 1), 0); $y = [math]::max(($offY + $ypos - 1), 0) $coords = New-Object Management.Automation.Host.Coordinates $x, $y # ... to the bottom right corner coords $rect = New-Object Management.Automation.Host.Rectangle ` $coords.X, $coords.Y, ($w - $xpos + 1), ($offY + $ypos + $menu.length + 4 + 1) # The original console contents will be restored later. $buffer = $Host.UI.RawUI.GetBufferContents($rect) function destroy { $Host.UI.RawUI.SetBufferContents($coords,$buffer) } $box = @{ "nw" = [char]0x2554 # northwest corner "ns" = [char]0x2550 # horizontal line "ne" = [char]0x2557 # northeast corner "ew" = [char]0x2551 # vertical line "sw" = [char]0x255A # southwest corner "se" = [char]0x255D # southeast corner "lsel" = [char]0x2192 # right arrow "rsel" = [char]0x2190 # left arrow } function WriteTo-Pos ([string]$str, [int]$x = 0, [int]$y = 0, [string]$bgc = $menubgc, [string]$fgc = $menufgc) { $saveY = [console]::CursorTop [console]::setcursorposition($x,$offY+$y) Write-Host $str -b $bgc -f $fgc -nonewline [console]::setcursorposition(0,$saveY) } # Wait for keypress of a recognized key, return virtual key code function getKey { # PgUp/PgDn + arrows + enter + 0-9 $valid = 33..34 + 37..40 + 13 + 48..(47 + [math]::min($menu.length, 10)) # 10=a, 11=b, etc. if ($menu.length -gt 10) { $valid += 65..(54 + $menu.length) } while (-not ($valid -contains $keycode)) { $keycode = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown').VirtualKeyCode } $keycode } # for centering the title and footer prompt function center([string]$what, [string]$fill = " ") { $lpad = $fill * [math]::max([math]::floor(($dialogwidth - 4 - $what.length) / 2), 0) $rpad = $fill * [math]::max(($dialogwidth - 4 - $what.length - $lpad.length), 0) "$lpad $what $rpad" } function menu { $y = $ypos WriteTo-Pos ($box.nw + (center $menutitle $box.ns) + $box.ne) $xpos ($y++) WriteTo-Pos ($box.ew + (" " * ($dialogwidth - 2)) + $box.ew) $xpos ($y++) # while $item can equal $menu[$i++] without error... for ($i=0; $item = $menu[$i]; $i++) { $rtpad = " " * [math]::max(($dialogwidth - 8 - $item.length), 0) if ($i -eq $selection) { WriteTo-Pos ($box.ew + " " + $box.lsel + " $item " + $box.rsel + $rtpad ` + $box.ew) $xpos ($y++) $menufgc $menubgc } else { # if $i is 2 digits, switch to the alphabet for labeling $idx = $i; if ($i -gt 9) { [char]$idx = $i + 55 } WriteTo-Pos ($box.ew + " $idx`: $item $rtpad" + $box.ew) $xpos ($y++) } } WriteTo-Pos ($box.sw + ([string]$box.ns * ($dialogwidth - 2) + $box.se)) $xpos ($y++) WriteTo-Pos (" " + (center $menuprompt) + " ") $xpos ($y++) 1 } while (menu) { [int]$key = getKey switch ($key) { 33 { $selection = 0; break } # PgUp/PgDn 34 { $selection = $menu.length - 1; break } 37 {} # left or up 38 { if ($selection) { $selection-- }; break } 39 {} # right or down 40 { if ($selection -lt ($menu.length - 1)) { $selection++ }; break } # letter, number, or enter default { # if alpha key, align with VirtualKeyCodes of number keys if ($key -gt 64) { $key -= 7 } if ($key -gt 13) {$selection = $key - 48} # restore the original console buffer contents destroy exit($selection) } } } 

那么,我可以在几个不眠之夜自己找到解决办法:

 @ECHO OFF :SELECTLOOP SETLOCAL EnableDelayedExpansion SET /A current=1 :subpick ECHO ------ SET /A count=0 FOR /F "delims=" %%M IN ("ONE TWO THREE FOUR FIVE") DO FOR %%N IN (%%M) DO ( SET /A count+=1 SET str!count!=%%N IF !count! NEQ !current! (ECHO %%N) ELSE (ECHO ^>%%N^<)) CHOICE /N /C zxc /m:"------" IF %errorlevel% EQU 0 ECHO error message & GOTO SELECTLOOP IF %errorlevel% EQU 1 set /A current-=1 & GOTO subcheck IF %errorlevel% EQU 2 set /A current+=1 & GOTO subcheck IF %errorlevel% EQU 3 (ECHO !current! IS CONFIRMED) & (TIMEOUT 3 >NUL) :subcheck CLS IF !current! LSS 1 SET /A current=1 IF !current! GTR !count! SET /A current=!count! GOTO subpick PAUSE