如何在命令行上传递一系列值-将表达式作为参数传递 [英] how do I pass a range of values on the command line - passing an expression as an argument

查看:62
本文介绍了如何在命令行上传递一系列值-将表达式作为参数传递的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码:

$srv_range = 29..30+40+50..52
$srv_range.GetType()
$NewVMTemplate = New-Object psobject
$NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null

$srv_range | % {
    $pod= $_
    $servers = @()
    1..2 | % {
        $server = $NewVMTemplate | Select-Object *
        $server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
        $servers += $server
    }
    ForEach ( $server in $servers) {
        write-host $server.Name
    }
} 

输出:

PowerCLI C:\ .\eraseme.ps1

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
pod29-srv1
pod29-srv2
pod30-srv1
pod30-srv2
pod40-srv1
pod40-srv2
pod50-srv1
pod50-srv2
pod51-srv1
pod51-srv2
pod52-srv1
pod52-srv2

我想从CLI输入范围,但是用此代码得到以下输出

param(

    [Parameter(Mandatory=$False)] $srv_range

)
#$srv_range = 29..30+40+50..52
$srv_range.GetType()
$NewVMTemplate = New-Object psobject
$NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null

$srv_range | % {
    $pod= $_
    $servers = @()
    1..2 | % {
        $server = $NewVMTemplate | Select-Object *
        $server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
        $servers += $server
    }
    ForEach ( $server in $servers) {
        write-host $server.Name
    }
} 

PowerCLI C:\ .\eraseme.ps1 29..30+40+50..52

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object
pod29..30+40+50..52-srv1
pod29..30+40+50..52-srv2

如何从CLI输入范围并获得与第一个代码相同的结果?

解决方案

您的问题是,在您的.\eraseme.ps1 29..30+40+50..52调用中,参数 29..30+40+50..52被视为字符串文字-这是 not 被识别为表达式.

要将强制识别为表达式,只需将参数包含在(...) 中:

.\eraseme.ps1 (29..30+40+50..52)

请注意,您可以通过使用更具体的类型声明参数来增强脚本的健壮性,在这种情况下,使用字符串调用它的尝试将立即失败:

 [Parameter(Mandatory=$False)] [int[]] $srv_range

(其他优化也可以应用于您的脚本.)


可选的背景信息

相对于参数中的(可扩展)字符串而言,何时将不带引号的标记视为表达式模式 (请参见 about_Parsing ):

  • (...)$(...)@(...) 或令牌起始处的 创建一个 new解析上下文 ,其中可以使用表达式甚至嵌套命令:

    • (...)对于单个表达式或命令就足够了. $(...)(子表达式运算符)可以包含多个表达式/命令; @()( array 子表达式运算符)也可以,并且另外确保将其输出始终视为 array .

    • 值得注意的是,以下表达式不被 识别,而没有被上述表达式之一包围:

      • [...](类型文字)并访问其成员,例如[Environment]::Version
      • ..(范围表达式),例如1..10
    • 如果在标记的开头中,(...)$(...)@(...)后跟其他字符,则第一个其他字符被视为a的开头新的独立论点.

    • 相比之下,如果它们以不带引号的文字仅变量引用 开头,则$(...)的作用类似于"..."(可扩展字符串),(...)开始作为表达式的新自变量,并且@(...)被当作文字@,而(...)再次启动新的自变量,即表达.
  • 一个@,后跟一个变量的名称(例如,@params),其中包含参数值的集合或哈希表,可启动此答案以获取更多信息).

    • 如有必要,请将{...}中的变量名与后续字符区分开来(例如${HOME}).
  • 访问变量值的属性或使用 index 或调用方法或嵌入任意命令,您必须将表达式括在$(...) 中,例如v$($PSVersionTable.PSVersion)

  • 通常,最安全的方法是将标记与嵌入的变量引用/表达式一起包含在"..." 中,因为这样可以避免以下情况:

      未引用令牌的开始处的
    • $(...) not 解释为可扩展字符串的一部分,被视为单独的参数(例如,Write-Output $('ab')c产生两个参数:$('ab')和立即数c的结果).
    • 在令牌的开始处的
    • . 紧随其后的是简单的变量引用或子表达式,也会导致单独的参数 .
      (例如,.$HOME产生两个参数:文字.$HOME的值)
  • 注意:即使扩展结果是字符串,也不一定保持一个:最终类型取决于参数的类型扩展值所绑定到的命令.

  • 转义/引用:

    • PowerShell比cmd.exe具有更多的元字符,并且值得注意的陷阱是必须将,逸出才能用作文字,因为,是PowerShell的数组构造运算符.

    • 转义单个字符,请在其前面加上 `(反引号).

    • 避免分别 转义元字符,请将值包含在 "..."(双引号) '...'(单引号):

      • 如果希望对字符串进行内插(扩展),即使用双引号,例如嵌入变量引用和子表达式.

        • 内部用双引号引起来的字符串`-跳出以下字符.将它们视为文字:` " $
      • 使用单引号将值视为文字 .

        • 内部用单引号引起来的字符串,将'换为''
    • 单引号或双引号通常是在值中转义空格的最简单方法.

  • 最后,请注意, --% ,即所谓的停止分析符号(PSv3 +),完全改变了所有剩余参数的解释:如果要与旧版cmd.exe命令行一起使用,它会停止解释该行的其余部分,除非扩展cmd.exe样式的%...%环境变量.请参见 Get-Help about_Parsing


  • 关于使用带引号的令牌:

    • '...'"..." 在令牌开始处:

      • 这些照常进行解析:作为文字('...')或可扩展("...")字符串.
      • 任何其他字符都会导致第一个其他字符被视为新的单独参数的开头.
    • '...'"..."不带引号的文字仅变量引用

      • 将像往常一样对它们进行评估,并将结果(即删除引号)附加到它们之前(评估为).

    [1] 参数模式元字符(具有特殊句法含义的字符)是:
    <space> ' " ` , ; ( ) { } | & < > @ #.
    其中,< > @ #仅在令牌的开始处是特殊的.


    示例

    Write-Output 1..10    # STRING: -> '1..10'
    Write-Output (1..10)  # EXPRESSION: -> @(1, 2, ...)
    # Write-Output $(1..10) would work too, but is only necessary if 
    # the enclosed expression comprises *multiple* statements.
    
    Write-Output [Environment]::Version  # STRING: -> '[Environment]::Ticks'
    Write-Output ([Environment]::Version)  # EXPRESSION: -> a [System.Version] instance.
    
    Write-Output a,b    # !! ARRAY @(1, 2), because "," is not escaped.
    Write-Output a`,b   #`# STRING 'ab'                                 
    Write-Output "a,b"  # ditto
    Write-Output 'a,b'  # ditto
    
    Write-Output $HOME\Desktop   # EXPANDED string (e.g.) 'C:\Users\jdoe\Desktop'
    Write-Output "$HOME\Desktop" # ditto
    Write-Output '$HOME\Desktop' # LITERAL string '$HOME\Desktop'
    Write-Output dir=$HOME       # EXPANDED string (e.g.) 'dir=C:\Users\jdoe\Desktop'
    
    Write-Output $PSVersionTable.PSVersion           # a [System.Version] instance
    Write-Output "$($PSVersionTable.PSVersion)/more" # a [string]; e.g., '5.1.14393.576/more'
    Write-Output "v$($PSVersionTable.PSVersion)"     # ditto; e.g., 'v5.1.14393.576'
    
    # !!! These DO NOT WORK as intended.
    Write-Output $($PSVersionTable.PSVersion)/more # $(...) at the *start*
    Write-Output $PSVersionTable.PSVersion/more    # $(...) missing
    Write-Output "$PSVersionTable.PSVersion/more"  # $(...) missing
    Write-Output .$HOME # Specifically, .$ at the beginning is the problem; escaping . works
    

    I have the following code:

    $srv_range = 29..30+40+50..52
    $srv_range.GetType()
    $NewVMTemplate = New-Object psobject
    $NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null
    
    $srv_range | % {
        $pod= $_
        $servers = @()
        1..2 | % {
            $server = $NewVMTemplate | Select-Object *
            $server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
            $servers += $server
        }
        ForEach ( $server in $servers) {
            write-host $server.Name
        }
    } 
    

    output:

    PowerCLI C:\ .\eraseme.ps1
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Object[]                                 System.Array
    pod29-srv1
    pod29-srv2
    pod30-srv1
    pod30-srv2
    pod40-srv1
    pod40-srv2
    pod50-srv1
    pod50-srv2
    pod51-srv1
    pod51-srv2
    pod52-srv1
    pod52-srv2
    

    I want to input the range from CLI, but I get the following output with this code

    param(
    
        [Parameter(Mandatory=$False)] $srv_range
    
    )
    #$srv_range = 29..30+40+50..52
    $srv_range.GetType()
    $NewVMTemplate = New-Object psobject
    $NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null
    
    $srv_range | % {
        $pod= $_
        $servers = @()
        1..2 | % {
            $server = $NewVMTemplate | Select-Object *
            $server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
            $servers += $server
        }
        ForEach ( $server in $servers) {
            write-host $server.Name
        }
    } 
    
    PowerCLI C:\ .\eraseme.ps1 29..30+40+50..52
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     String                                   System.Object
    pod29..30+40+50..52-srv1
    pod29..30+40+50..52-srv2
    

    How can I input the range from CLI and get the same result as the first code?

    解决方案

    Your problem is that argument 29..30+40+50..52 is treated as a string literal in your .\eraseme.ps1 29..30+40+50..52 call - it is not recognized as an expression.

    To force recognition as an expression, simply enclose the argument in (...):

    .\eraseme.ps1 (29..30+40+50..52)
    

    Note that you can make your script more robust by declaring your parameter with a more specific type, in which case an attempt to call it with a string would fail right away:

     [Parameter(Mandatory=$False)] [int[]] $srv_range
    

    (Other optimizations could be applied to your script as well.)


    Optional background information

    As for when an unquoted token is treated as an expression vs. an (expandable) string in argument mode (see about_Parsing):

    • (...), $(...), and @(...) by themselves or at the start of a token create a new parsing context, in which expressions or even nested commands can be used:

      • (...) is sufficient for a single expression or command. $(...) (the subexpression operator) can enclose multiple expressions / commands; so can @() (the array subexpression operator), and it additionally ensures that its output is always treated as an array.

      • Notably, the following expressions are not recognized without being enclosed in one of the above:

        • [...] (type literals) and access to their members, such as [Environment]::Version
        • .. (range expressions) such as 1..10
      • If, at the start of a token, (...), $(...), or @(...) are followed by additional characters, the first additional character is considered the start of a new, separate argument.

      • By contrast, if they're preceded by an unquoted literal or a variable-only reference, $(...) works like inside "..." (an expandable string), (...) starts a new argument that is an expression, and @(...) is taken as literal @ with (...) again starting a new argument that is an expression.
    • A @ followed by the name of a variable (e.g., @params) containing a collection or hashtable of parameter values initiates parameter splatting.

    • @{ ... } can be used to pass a hashtable literal (e.g., @{ key = 'value' }).

    • { ... } creates a script block ([scriptblock]).

    • By themselves or at the start of a token, variable references, including member access (property access, method calls, indexing) can be used as-is:

      • Expressions such as $HOME, $PSVersionTable.PSVersion, $someArray[0], and $someString.ToUpper() are recognized, and returned as their inherent type.

      • Without member access, i.e., with a simple variable reference such as $HOME, subsequent characters are (potentially) considered part of the same argument that is then interpreted as an expandable string - see below.

      • With member access, the first of any additional characters is considered the start of a new argument (e.g., $foo.Length-more results in two arguments: the value of $foo.Length and string literal -more).

    • Everything else is treated as an expandable string, i.e., similar to the contents of a double-quoted string, except that metacharacters[1] still need escaping and certain tokens are interpreted as multiple arguments.

      • Expandable means that embedded simple variable references (e.g., $HOME\Desktop or $env:APPDATA\Test) are interpolated (replaced with their stringified values).
        Note that this can result in a representation that differs from a given value's default output format as shown in the console, for instance (again, see this answer for more information).

        • Enclose a variable name in {...} to disambiguate it from subsequent characters, if necessary (e.g., ${HOME}).
      • To access a variable value's property or use an index or call a method or embed arbitrary commands, you must enclose the expression in $(...), e.g., v$($PSVersionTable.PSVersion)

      • Generally, it is safest to enclose tokens with embedded variable references / expressions in "...", because it avoids the following edge cases:

        • $(...) at the start of an unquoted token is not interpreted as part of an expandable string, it is treated as a separate argument (e.g., Write-Output $('ab')c results in two arguments: the result of $('ab') and literal c).
        • . at the start of a token immediately followed by a simple variable reference or subexpression results in separate arguments too.
          (E.g., .$HOME results in two arguments: literal ., and the value of $HOME)
      • Note: Even though the result of the expansion is a string, it doesn't necessarily remain one: the final type is determined by the type of to the parameter of the command at hand to which the expanded value is bound.

      • Escaping / quoting:

        • PowerShell has many more metacharacters than cmd.exe, and a notable pitfall is that , must be escaped to be treated a literal, because , is PowerShell's array-construction operator.

        • To escape a single character, prefix it with ` (backtick).

        • To avoid the need for escaping metacharacters individually, enclose the value in "..." (double quotes) or '...' (single quotes):

          • Use double quotes if you want the string to be interpolated (expanded), i.e., if you want to be able to embed variable references and subexpressions.

            • Inside a double-quoted string, `-escape the following chars. to treat them as literals: ` " $
          • Use single quotes to treat the value as a literal.

            • Inside a single-quoted string, escape a ' as ''
        • Single- or double-quoting is usually the easiest way to escape spaces in a value.

    • Finally, note that --%, the so-called stop-parsing symbol (PSv3+), completely changes the interpretation of all remaining arguments: designed for use with legacy cmd.exe command lines, it stops interpreting the rest of the line except for expansion of cmd.exe-style %...% environment variables. See Get-Help about_Parsing


    As for using quoted tokens:

    • '...' or "..." by themselves or at the start of a token:

      • These are parsed as as usual: as a literal ('...') or expandable ("...") string.
      • Any additional characters cause the first additional character to be considered the start of a new, separate argument.
    • '...' or "..." being preceded by an unquoted literal or variable-only reference:

      • They are evaluated as usual and the result (i.e., with quotes removed) is appended to what precedes them (evaluated to).

    [1] The argument-mode metacharacters (characters with special syntactic meaning) are:
    <space> ' " ` , ; ( ) { } | & < > @ #.
    Of these, < > @ # are only special at the start of a token.


    Examples

    Write-Output 1..10    # STRING: -> '1..10'
    Write-Output (1..10)  # EXPRESSION: -> @(1, 2, ...)
    # Write-Output $(1..10) would work too, but is only necessary if 
    # the enclosed expression comprises *multiple* statements.
    
    Write-Output [Environment]::Version  # STRING: -> '[Environment]::Ticks'
    Write-Output ([Environment]::Version)  # EXPRESSION: -> a [System.Version] instance.
    
    Write-Output a,b    # !! ARRAY @(1, 2), because "," is not escaped.
    Write-Output a`,b   #`# STRING 'ab'                                 
    Write-Output "a,b"  # ditto
    Write-Output 'a,b'  # ditto
    
    Write-Output $HOME\Desktop   # EXPANDED string (e.g.) 'C:\Users\jdoe\Desktop'
    Write-Output "$HOME\Desktop" # ditto
    Write-Output '$HOME\Desktop' # LITERAL string '$HOME\Desktop'
    Write-Output dir=$HOME       # EXPANDED string (e.g.) 'dir=C:\Users\jdoe\Desktop'
    
    Write-Output $PSVersionTable.PSVersion           # a [System.Version] instance
    Write-Output "$($PSVersionTable.PSVersion)/more" # a [string]; e.g., '5.1.14393.576/more'
    Write-Output "v$($PSVersionTable.PSVersion)"     # ditto; e.g., 'v5.1.14393.576'
    
    # !!! These DO NOT WORK as intended.
    Write-Output $($PSVersionTable.PSVersion)/more # $(...) at the *start*
    Write-Output $PSVersionTable.PSVersion/more    # $(...) missing
    Write-Output "$PSVersionTable.PSVersion/more"  # $(...) missing
    Write-Output .$HOME # Specifically, .$ at the beginning is the problem; escaping . works
    

    这篇关于如何在命令行上传递一系列值-将表达式作为参数传递的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆