如何创建带有可选参数的批处理文件

How to create a batch file that takes optional parameters

我不是 Windows 人,来自 Linux 方面。 我的问题是,如何编写一个将可选开关作为布尔值或值参数的批处理文件?例如:

ssh.cmd /X /l 登录远程主机

其中 /X 是一个布尔开关,意思是 "turn on feature X",而 /l 是一个 "value switch",意思是命令行上的下一个标记将以特殊方式使用。例如,在 unix 中,您可以这样做:

ssh -X -l 登录远程主机

这里,-X 是一个布尔值,告诉 ssh 转发 X 端口,-l 是一个 "value switch",它告诉 ssh 我的用户名是 "login"。 remotehost 是强制参数。我知道如何处理命令文件中的最后一个,并且无法在 Googlespace 的任何地方找到解决方案。

我认为做到这一点的唯一方法是痛苦地浏览参数,寻找符合正确格式的字符串,然后说 "aha! found a switch!" 对吗?

提前致谢。

@echo off
setlocal EnableDelayedExpansion

rem Define the "value switches"
set "valSwitch=L M N"

rem Load parameters in "switch" and "argv" vectors
set "argc=0"

:nextParam
   set "param=%~1"
   if "%param:~0,1%" equ "/" (

      rem Is a switch
      for %%s in (%param:~1,1%) do (
         if "!valSwitch:%%s=!" equ "%valSwitch%" (
            rem Is a boolean switch
            set "switch[%%s]=True"
         ) else (
            rem Is a value switch
            set "switch[%%s]=%~2"
            shift
         )
      )

   ) else (

      rem Is a standard parameter
      set /A argc+=1
      set "argv[!argc!]=%~1"

   )

   shift
if "%~1" neq "" goto nextParam

SET SWITCH
SET ARGV

输出示例:

C:\Users\Antonio\Test> test.bat /X /l login remotehost
switch[l]=login
switch[X]=True
argv[1]=remotehost

C:\Users\Antonio\Test> test.bat /X remotehost /l login
switch[l]=login
switch[X]=True
argv[1]=remotehost

C:\Users\Antonio\Test> test.bat remotehost /l login /X
switch[l]=login
switch[X]=True
argv[1]=remotehost

C:\Users\Antonio\Test> test.bat /Y /A /M "login1 login2" remotehost "last parameter"
switch[A]=True
switch[M]=login1 login2
switch[Y]=True
argv[1]=remotehost
argv[2]=last parameter

这是另一个拒绝无法识别的开关的选项,接受 /- 作为有效的开关前缀,执行 case-insensitive 开关检查,并 ping 远程主机以检查有效性。

@echo off
setlocal enabledelayedexpansion

if not "%~1"=="" goto :switches

:usage
echo Usage: %~nx0 [/X] [/L loginname] remotehost
goto :EOF

:switches
rem // make sure variables are undefined
for %%I in (forward login host switch valid) do set "%%I="

rem // loop through all arguments
for %%I in (%*) do (

    rem // if this iteration is a value for the preceding switch
    if defined switch (

        rem // if that switch was -L or /L (case-insensitive)
        if /i "!switch:~1!"=="L" set "login=%%~I"

        rem // clear switch variable
        set "switch="

    ) else (

        rem // if this iteration is a switch
        echo(%%~I | >NUL findstr "^[-/]" && (

            set "switch=%%~I"

            rem // if this is a valid switch
            for %%x in (X L) do (
                if /i "!switch:~1!"=="%%x" set "valid=true"
            )

            if not defined valid (
                echo Unrecognized switch: %%~I
                goto usage
            )

            set "valid="

            if /i "!switch:~1!"=="X" (
                set "forward=true"
                set "switch="
            )
        ) || (

            rem // not a switch.  Must be a host.
            ping -n 1 %%~I | findstr /i "^Reply.*time.*[0-9]" >NUL && (
                set "host=%%~I"
            ) || (
                echo Host %%~I is unreachable.
            )
        )
    )
)

if not defined host goto usage

<NUL set /P "=Args passed: "
if defined forward <NUL set /P "=FORWARD "
if defined login <NUL set /P "=LOGIN %login% "
echo %host%

而且您知道,如果您不习惯编写脚本 Windows 批处理脚本,您可以考虑另一种脚本语言。许多人会建议使用 PowerShell,这是一个值得的选择。但是,如果您不想学习一门全新的语言和格式,如果您熟悉 JavaScript,那么 JScript 非常相似。我将进行演示,希望您能够看到相似之处。使用 .js 扩展名保存它。要 运行 它,请执行 cscript /nologo scriptname.js switches here:

// convert WSH.Arguments object to a JS array
for (var args=[], i=0; i<WSH.Arguments.Length; i++)
    args.push(WSH.Arguments(i));

function error(txt, code) {
    WSH.StdErr.WriteLine(txt);
    WSH.StdErr.WriteLine('Usage: '+WSH.ScriptName+' [/X] [/L loginname] remotehost');
    WSH.Quit(code);
}

for (var i=0; i<args.length; i++) {
    switch (args[i].toLowerCase().replace("-","/")) {

        case "/x":
            var forward = true;
            break;

        case "/l":
            if (++i == args.length || /^\//.test(args[i]))
                error('Invalid login name.', 1);
            else var login = args[i];
            break;

        default:
            if (/^\//.test(args[i]))
                error('Unrecognized option: ' + args[i], 2);
            else var host = args[i];
    }
}

if (!host) error('No host specified.', 3);

WSH.Echo([
    'Command: ',
    (forward ? 'FORWARD ' : ''),
    (login ? 'LOGIN ' + login + ' ' : ''),
    host
].join(''));

JScript 和 JavaScript 有很多共同点。您可以调用相同的 RegExp 对象方法和 Array 对象方法、修改原型、将数据强制转换为兼容的数据类型或测试它们的真实性,等等。 JScript JavaScript,只是具有 "WScript" 的根祖先对象(或 "WSH" 懒惰)而不是 "Window"。

// convert WSH.Arguments proprietary object to a JS array
for (var args=[], i=0; i<WSH.Arguments.Length; i++)
    args.push(WSH.Arguments(i));

function error(txt, code) {
    WSH.StdErr.WriteLine(txt);
    WSH.StdErr.WriteLine('Usage: '+WSH.ScriptName+' [/X] [/L loginname] remotehost');
    WSH.Quit(code);
}

// returns idx of array element matching rxp (or -1 if no match)
Array.prototype.match = function(rxp) {
    for (var i=this.length; i--;) if (rxp.test(this[i])) return i;
    return -1;
}

var X = args.match(/^[/-]X$/i);
if (X > -1) var forward = args.splice(X, 1)[0];

var L = args.match(/^[/-]L$/i);
if (L > -1) var login = args.splice(L, 2)[1];

for (var invalid=[], i=args.length; i--;)
    if (/^[/-]/.test(args[i])) invalid.push(args[i]);

if (invalid.length) error('Unrecognized option: ' + invalid.join(), 1);
if (!args.length) error('No host specified.', 2);

WSH.Echo([
    'Params: ',
    (forward ? 'FORWARD ' : ''),
    (login ? 'LOGIN ' + login + ' ' : ''),
    args[0]
].join(''));

如果您想使用这些参数启动外部程序,请查看 objShell.Run


如果您想将您的脚本部署到其他用户,将其制作成 Batch + JScript 混合格式并使用 .bat 扩展名保存它可以使它更 user-friendly 执行。只需将其放在上述任一解决方案的顶部,并使用 .bat 扩展名保存脚本。

@If (@CodeSection == @Batch) @then
@echo off & setlocal
cscript /nologo /e:JScript "%~f0" %*
goto :EOF
@end // end Batch / begin JScript hybrid chimera
// JScript code here

它的工作方式是,首先,脚本使用 cmd 批处理解释器加载。由于 (@CodeSection 不等于 @Batch),因此永远不会尝试该行的其余部分。继续直到 cscript 行,它使用 JScript 解释器重新加载此脚本,同时传递命令行参数。

JScript 确定 @CodeSection 实际上不等于 @Batch,并跳到 @end。在 JScript 执行结束时,cscript.exe 退出并且 Batch 线程恢复,直到 goto :EOFexit /b.

添加混合代码后,语法为 scriptname.bat arguments here。如果您想绕过批处理代码并只执行 JScript 部分,cscript /nologo /e:JScript scriptname.bat argumentss here 也可以。