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

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

问题描述

我有以下代码:

$srv_range = 29..30+40+50..52$srv_range.GetType()$NewVMTemplate = 新对象 psobject$NewVMTemplate |Add-Member -MemberType NoteProperty -Name Name -Value $null$srv_range |%{$pod= $_$servers = @()1..2 |%{$server = $NewVMTemplate |选择对象 *$server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_$servers += $server}ForEach($servers 中的 $server){写主机 $server.Name}}

输出:

PowerCLI C:.eraseme.ps1IsPublic IsSerial 名称 BaseType——————————————————真真对象[] System.Arraypod29-srv1pod29-srv2pod30-srv1pod30-srv2pod40-srv1pod40-srv2pod50-srv1pod50-srv2pod51-srv1pod51-srv2pod52-srv1pod52-srv2

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

param([参数(Mandatory=$False)] $srv_range)#$srv_range = 29..30+40+50..52$srv_range.GetType()$NewVMTemplate = 新对象 psobject$NewVMTemplate |Add-Member -MemberType NoteProperty -Name Name -Value $null$srv_range |%{$pod= $_$servers = @()1..2 |%{$server = $NewVMTemplate |选择对象 *$server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_$servers += $server}ForEach($servers 中的 $server){写主机 $server.Name}}PowerCLI C:.eraseme.ps1 29..30+40+50..52IsPublic IsSerial 名称 BaseType——————————————————真真字符串 System.Objectpod29..30+40+50..52-srv1pod29..30+40+50..52-srv2

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

解决方案

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

强制识别为表达式,请将参数括在(...)中,分组运算符:

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

同样适用,如果你想使用(另一个)命令的输出作为命令参数;例如:

# 将从文件paths.txt 读取的行作为数组传递给Get-ChildItem#(两个命令中都隐含了参数 -Path).Get-ChildItem(获取内容路径.txt)

两点:
$(...)子表达式运算符,只在两种情况下需要:(a) 嵌入整个语句,特别是循环和条件,在另一个语句中,以及 (b) 在 "..." 中嵌入表达式、命令或语句,这是一个可扩展(插值)字符串.只需 (...) 就足以在语句中嵌入单个 command表达式(即使在 RHS 上也不需要)变量赋值).虽然不太可能,但不必要地使用 $(...) 会产生副作用 - 请参阅这个答案.
• 您可以通过使用更具体的类型声明参数使您的脚本更健壮,在这种情况下,尝试使用字符串调用它会立即失败:
[Parameter(Mandatory=$False)] [int[]] $srv_range
(其他优化也可以应用于您的脚本.)


可选背景信息

至于何时将未加引号的标记视为表达式嵌套命令(可扩展)参数模式中的字符串(另见:about_Parsing):

  • (...)$(...)@(...)自己在标记的开头创建一个新的解析上下文,其中表达式甚至嵌套命令 可以使用:

    • (...) 对于单个 表达式或命令就足够了.$(...)(子表达式运算符)可以包含多个表达式/命令;@()(array 子表达式运算符)也是如此,它还确保其输出始终被视为一个 array.

    • 值得注意的是,如果不包含在上述表达式之一中,则不能识别以下表达式:

      • [...](类型文字)和对其成员的访问,例如 [Environment]::Version
      • ..(范围表达式)例如 1..10
    • 如果,在标记的开始(...)$(...),或@(...) 后跟附加字符,第一个附加字符被认为是新的、单独的参数的开始.

    • 相比之下,如果它们前面不带引号的文字变量引用$(...) 的工作原理类似于 "..."(一个可扩展的字符串),(...) 开始一个 new参数是一个表达式,并且 @(...) 再次被当作文字 @(...)开始一个作为表达式的新参数.

  • A @ 后跟变量名(例如,@params)包含参数值的集合或哈希表启动参数设置.>

  • @{ ... } 可用于传递哈希表文字(例如,@{ key = 'value' }).

  • { ... } 创建一个脚本块 ([scriptblock]).

  • 自身在令牌的开头,变量引用,包括成员访问(属性访问,方法调用、索引)可以按原样使用:

    • 诸如 $HOME$PSVersionTable.PSVersion$someArray[0]$someString.ToUpper() 被识别,并作为其固有类型返回.
  • 没有成员访问权限,即使用一个简单的变量引用,例如$HOME,后续字符(可能)被认为是相同的一部分 参数,然后被解释为 可扩展字符串 - 见下文.

  • 对于成员访问,任何附加字符中的第一个被视为新参数的开始(例如,$foo.Length-more 导致 两个 参数:$foo.Length 的值和字符串文字 -more).

  • 其他所有内容都被视为可扩展字符串,即类似于双引号字符串的内容,除了元字符[1]仍然需要转义某些标记被解释为多个参数.

    • 可扩展意味着嵌入简单变量引用(例如,$HOMEDesktop$env:APPDATATest) 被插入(替换为它们的字符串化值).
      请注意,例如,这可能会导致与控制台中显示的给定值的默认输出格式不同的表示(再次参见 this回答以获取更多信息).

      • 如有必要,在 {...} 中将变量名称括起来,以消除其与后续字符的歧义(例如,${HOME}).
    • 访问变量值的属性或使用索引或调用方法或嵌入任意命令,你必须将表达式括在$(...)中,例如,v$($PSVersionTable.PSVersion)

    • 一般来说,"..."中用嵌入的变量引用/表达式括起来是最安全的,因为它避免了以下边缘情况:* $(...) 位于未加引号的标记的开始不会被解释为可扩展字符串的一部分,它被视为一个单独的参数(例如,Write-Output $('ab')c 导致两个参数:结果$('ab') 和文字 c).* . 在标记的开始紧跟一个简单的变量引用或子表达式导致单独的参数em>也是.
      (例如,.$HOME 导致 两个 参数:文字 .$HOME 的值)

      • 注意:即使展开的结果是一个字符串,也不一定保持一个:最终类型由参数的类型决定 扩展值绑定到的手头命令.

      • 转义/引用:

        • PowerShell 具有比 cmd.exe 多得多的元字符,并且一个值得注意的缺陷是 , 必须被转义才能被视为文字,因为 , 是 PowerShell 的数组构造运算符.
      • 转义单个字符,请在其前面加上`(反引号).>

      • 为了避免转义元字符单独,请将值括在..."(双引号)'...'(单引号):

        • 如果您希望字符串被插入(扩展),请使用双引号,即,如果您希望能够嵌入变量引用和子表达式.

          • Inside 一个双引号字符串,` - 转义以下字符.将它们视为文字: ` "$
        • 使用单引号将值视为文字.

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

  • 最后,请注意--%,即所谓的停止解析符号(PSv3+),彻底改变了所有剩余参数的解释:设计用于旧的 cmd.exe 命令行,它停止解释该行的其余部分,除了 cmd.exe 的扩展-样式 %...% 环境变量.请参阅 获取帮助 about_Parsing


至于使用引用标记:

  • '...'..." 自己在一个令牌:

    • 像往常一样解析这些:作为文字 ('...') 或可扩展的 ("...") 字符串.
    • 任何附加字符都会导致第一个附加字符被视为新的、单独的参数的开始.
  • '...'..." 前面 未加引号的文字变量引用:

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

[1] 参数模式元字符(具有特殊句法含义的字符)是:
<代码><空格>' ", ;( ) { } |&<>@#
.
其中,<>@# 仅在标记的 start 处是特殊的.


示例

Write-Output 1..10 # STRING: ->'1..10'写输出 (1..10) # 表达式:->@(1, 2, ...)# Wr​​ite-Output $(1..10) 也可以,但只有在# 封闭的表达式包含*多个*语句.写输出[环境]::版本#字符串:->'[环境]::滴答声'写输出([环境]::版本)#表达式:->[System.Version] 实例.写输出 a,b # !!ARRAY @(1, 2), 因为,"没有逃脱.写输出 a`,b #`# STRING 'ab'写输出a,b"#同上写输出 'a,b' # 同上写输出 $HOMEDesktop # EXPANDED 字符串(例如)'C:UsersjdoeDesktop'写输出$HOMEDesktop"#同上写输出 '$HOMEDesktop' # LITERAL 字符串 '$HOMEDesktop'Write-Output dir=$HOME # EXPANDED string (e.g.) 'dir=C:UsersjdoeDesktop'写输出 $PSVersionTable.PSVersion # 一个 [System.Version] 实例写输出$($PSVersionTable.PSVersion)/more"#一个[字符串];例如,5.1.14393.576/更多"写输出v$($PSVersionTable.PSVersion)"# 同上;例如,'v5.1.14393.576'#!!!这些不能按预期工作.写输出 $($PSVersionTable.PSVersion)/more # $(...) 在*开始*写输出 $PSVersionTable.PSVersion/more # $(...) 丢失写输出$PSVersionTable.PSVersion/more"# $(...) 缺失Write-Output .$HOME # 具体就是.$开头的问题;逃跑.作品

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, enclose the argument in (...), the grouping operator:

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

The same applies if you want to use output from (another) command as a command argument; e.g.:

# Pass the lines read from file paths.txt as an array to Get-ChildItem
# (Parameter -Path is implied in both commands).
Get-ChildItem (Get-Content paths.txt)

Two asides:
$(...), the subexpression operator, is only ever needed in two cases: (a) to embed entire statement(s), notably loops and conditionals, in another statement, and (b) to embed an expression, command, or statement(s) inside "...", an expandable (interpolating) string. Just (...) is enough to embed a single command or expression in a statement (and even that isn't needed on the RHS of a variable assignment). While not likely, the unnecessary use of $(...) can have side effects - see this answer.
• 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 or nested command vs. an (expandable) string in argument mode (see also: 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., $HOMEDesktop or $env:APPDATATest) 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 $HOMEDesktop   # EXPANDED string (e.g.) 'C:UsersjdoeDesktop'
Write-Output "$HOMEDesktop" # ditto
Write-Output '$HOMEDesktop' # LITERAL string '$HOMEDesktop'
Write-Output dir=$HOME       # EXPANDED string (e.g.) 'dir=C:UsersjdoeDesktop'

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天全站免登陆