模拟在PowerShell中使用管道(pipe)将bash的`ls -ltr`和`grep`命令。

huangapple go评论62阅读模式
英文:

Emulate bash ls -ltr pipe grep in PowerShell

问题

我使用以下脚本来模拟 bash

    ls -ltr /www/root/*html| grep 'string' | grep 'narrower search string'

我在记事本中打开结果,因为从 PowerShell 终端剪切和粘贴很困难。

当有两个参数时它可以正常工作,但是当我只有一个参数时它工作不好,例如:

    ls -ltr /www/root/*html| grep 'string'

我知道如何在 bash 中使用 @ARGS。然而在 PowerShell 中我没有看到类似的东西。我没有看到任何用于验证 PowerShell 中的命令行参数的数组。

    param( 
    [parameter()]
    [string] $param1 = $null ,
    [string] $param2 = $null 
    )
    
    $makecurrent = '(dir sample_file.txt).LastWriteTime = Get-Date'
    
    $timestr = get-date -f 'MMddHHmmss'
    get-childitem -path "C:\www\root\*html" -Name | sort LastWriteTime -descending | out-file -filepath "c:\temp\lsfile$timestr.txt"
    select-string -path "c:\temp\lsfile$timestr.txt" -pattern "$param1" | out-file -filepath "c:\temp$param1$timestr.txt"
    select-string -path "c:\temp$param1$timestr.txt" -pattern "$param2" | out-file -filepath "c:\temp$param2$timestr.txt"
    $makecurrent | add-content -path "c:\temp$param2$timestr.txt"
    start "c:\temp$param2$timestr.txt"

我正在尝试让 PowerShell 脚本接受一个或两个参数。

英文:

I use the script below to to emulate bash

ls -ltr /www/root/*html| grep 'string' | grep 'narrower search string'

I open up the results in notepad - because it is so tough to cut and paste from the PowerShell terminal.

It works ok when there are two parameters - however it does not work well when I have just one parameter, like:

ls -ltr /www/root/*html| grep 'string'

I know how to use @ARGS in bash. However have not seen anything comparable in PowerShell. I don't see any kind of array to verify the line arguments in PowerShell.

param( 
[parameter()]
[string] $param1 = $null ,
[string] $param2 = $null 
)

$makecurrent = '(dir sample_file.txt).LastWriteTime = Get-Date'

$timestr = get-date -f 'MMddHHmmss'
get-childitem -path "C:\www\root\*html" -Name | sort LastWriteTime -descending | out-file -filepath "c:\temp\lsfile$timestr.txt"
select-string -path "c:\temp\lsfile$timestr.txt" -pattern "$param1" | out-file -filepath "c:\temp$param1$timestr.txt"
select-string -path "c:\temp$param1$timestr.txt" -pattern "$param2" | out-file -filepath "c:\temp$param2$timestr.txt"
$makecurrent | add-content -path "c:\temp$param2$timestr.txt"
start "c:\temp$param2$timestr.txt"

I am trying to get the PowerShell script to accept one or two parameters.

答案1

得分: 1

> 我知道如何在bash中使用`@ARGS`。然而在powershell中我没有看到类似的东西。我看不到任何用于验证powershell中的命令行参数的数组。

PowerShell的 **[自动变量`$args`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Automatic_Variables#args)** 相当于Bash的`$@` (`$*`) 参数:它们都包含以数组形式收集的单独传递的参数。

主要区别是:

* `$args` 仅包含参数(而不包含作为调用的脚本名称或路径的第一个元素,就像在Bash中的`${@:0:1}`一样)。

* 因此,它是 `$args[0]` 包含第一个参数(与`"${@:1:1}"` 对比),`$args[1]` 包含第二个(与`"${@:2:1}"` 对比),以此类推。

* `$args.Count` 包含传递的参数数量(类似于Bash的 `$#`)。

这意味着**严格来说,您不需要去 *声明* 参数**(通过一个 `param(...)` 块),如果您想接受一个不受限制的、开放式的参数数量,这将简化您的脚本:

```plaintext
# ... 不需要 param() 块

# ...

# 直接将包含所有参数的 $args 传递给 Select-String 的 -Pattern 参数
select-string -path "c:\temp\lsfile$timestr.txt" -pattern $args | 
  out-file -filepath "c:\temp$param1$timestr.txt"

更新

  • 后来我想到通过_链式_调用 grep 您正在寻找 合取 逻辑,也就是说只有包含 所有 模式的行;相比之下,将多个模式传递给单个 Select-String 应用_析取_逻辑,也就是说它找到包含 任意 模式的行。

  • 由于这篇帖子主要讨论参数声明和处理,我不会解决这个方面,但我可以指向一个 Select-StringAll 辅助函数,可以从 这个Gist 获取,它提供了所需的逻辑;您可以直接使用以下命令安装它:

irm https://gist.github.com/mklement0/356acffc2521fdd338ef9d6daf41ef07/raw/Select-StringAll.ps1 | iex
  • 我可以亲自保证这样做是安全的,但先查看源代码总是个好主意)。

  • 如果您更愿意自己实现这个逻辑,请参阅 Mathias R. Jessen 的答案

然而,您可能 想要 声明参数 出于以下原因:

  • 使用 数据类型

    • 声明的参数可以被声明为特定的.NET数据类型,比如 [int] (System.Int32) 或 [datetime] (System.DateTime),PowerShell的参数绑定器会自动强制要求给定的参数(值)是该类型或可以转换为它。
  • 使用 命名 参数:

    • 声明的参数可以以更具描述性的方式绑定(传递参数给它),通过在参数名称之前添加参数的名称;例如,如果您定义了一个参数 $Pattern,那么参数 foo 可以以 -Pattern foo 的方式传递给它,而不仅仅是 foo,后者是一个 位置 参数,其相对于其他类似参数的位置决定了它绑定到哪个参数。
  • 健壮性:

    • 您可以使参数成为 强制 的,也就是说必须传递一个参数给它们;如果您不这样做,您将会被 提示 提供值,但请注意,这个提示功能的用户体验很差 - 参见 这个答案 进行讨论和替代方案。

    • 如果您在 param(...) 块上方放置了一个 [CmdletBinding()] 属性或者至少使用了一个参数特定的 [Parameter()] 属性,那么您的脚本或函数隐式地成为了一个 高级 的脚本,此时参数绑定器会确保只有绑定到 声明的 参数的参数才能被传递。

    • 没有它,不能绑定到已声明的参数的参数仍然受到支持,并且会被收集在 $args 中(其中只包含这些所谓的 未绑定 参数)。

PowerShell使得声明 数组 参数变得容易 - 例如 [string[]] $Pattern 声明一个字符串数组。

  • 默认情况下,它们也需要一个 数组 作为它们的参数,这意味着在调用时一个 单一的参数,其数组元素用,分隔 - 例如,要传递模式 foobar,您必须使用以下方式:

    Pattern foo, bar
    
  • 要获得与自动 $args 变量相同的行为,也就是收集单独的参数到一个数组中,您可以使用一个 **`[Parameter(Value

英文:

<!-- language-all: sh -->

> I know how to use @ARGS in bash. However have not seen anything comparable in powershell. I don't see any kind of array to verify the line arguments in powershell.

PowerShell's automatic $args variable is the equivalent of Bash's $@ ($*) parameter: both contain the individually passed arguments collected in an array.

The main difference is:

  • $args contains arguments only (and not also a first element that contains the script name or path as invoked, as in Bash, ${@:0:1})

  • Therefore, it is $args[0] that contains the first argument (compare with &quot;${@:1:1}&quot;), $args[1] the second (compare with &quot;${@:2:1}&quot;), and so on.

  • $args.Count contains the number of arguments passed (the equivalent of Bash's $#).


This implies that you do not strictly need to declare parameters (via a param(...) block), if you want to accept an unconstrained, open-ended number of arguments, which would simplify your script to:

# ... no param() block needed

# ...

# Simply pass $args - containing all arguments -
# directly to Select-String&#39;s -Pattern parameter
select-string -path &quot;c:\temp\lsfile$timestr.txt&quot; -pattern $args | 
  out-file -filepath &quot;c:\temp$param1$timestr.txt&quot;

Update:

  • It occurred to me later that by chaining grep calls you're looking for conjunctive logic, i.e. only lines that contain all patterns; by contrast, passing multiple patterns to a single Select-String applies disjunctive logic, i.e. it finds lines that contain any of the patterns.

  • Since this post is primarily about parameter declaration and handling, I won't fix this aspect, but I can point you to a Select-StringAll helper function, available from this Gist, which provides the desired logic; you can install it directly with:

    irm https://gist.github.com/mklement0/356acffc2521fdd338ef9d6daf41ef07/raw/Select-StringAll.ps1 | iex
    
    • I can personally assure that doing so is safe, but it's always a good idea to look at the source code first).

    • If you'd rather implement the logic yourself, see Mathias R. Jessen's answer.


However, you may want to declare parameters for the following reasons:

  • Use of data types:

    • Declared parameters can be declared as a specific .NET data type, such as [int] (System.Int32) or [datetime] (System.DateTime), with PowerShell's parameter binder automatically enforcing that a given argument (value) is of that type or can be converted to it.
  • Use of named arguments:

    • A declared parameter can be bound (passed an argument to) in a more self-describing manner, by preceding it with the parameter's name; e.g., if you define a parameter $Pattern, an argument foo can be passed to it as -Pattern foo instead of just foo, the latter being a positional argument, whose position relative to other such arguments determines what parameter it binds to.
  • Robustness:

    • You can make parameters mandatory, i.e require that an argument be passed to them; if you don't, you'll get prompted for values, but note that the UX of this prompting feature is poor - see this answer for a discussion and alternatives.

    • If you place a [CmdletBinding()] attribute above your param(...) block or use at least one parameter-specific [Parameter()] attribute, your script or function implicitly becomes an advanced one, in which case the parameter binder ensures that only arguments that bind to declared parameters can be passed.

    • Without it, arguments that cannot be bound to declared parameters are still supported, and collected in $args (which then contains only these so-called unbound arguments).


PowerShell makes it easy to declare array parameters - e.g. [string[]] $Pattern to declare an array of strings.

  • By default they also require an array as their argument, meaning a single argument whose array elements are separated with , on invocation; e.g., to pass patterns foo and bar, you'd have to use
    Pattern foo, bar - note the required ,

  • To get the equivalent behavior as with the automatic $args variable - i.e. to collect separate arguments in an array, you can use a [Parameter(ValueFromRemainingArguments)] attribute to decorate the target parameter, which automatically collects all positional arguments in that parameter, while allowing other parameters, if any, to be bound by name.

  • You still have the option of binding such a parameter by name, in which case you again have to pass a single array as the argument, however.

  • In other words: You can bind a ValueFromRemainingArguments parameter named, say, -Pattern, in one of two ways:

    • Named, with an array (note the ,):

      Some-Command -Pattern foo, bar
      
    • Positional (unnamed), with individual arguments:

      Some-Command foo bar
      

Applied to your script:

param(
  # Declare -Pattern as an array of strings that can also
  # be bound with individual, positional arguments.
  [Parameter(Mandatory, ValueFromRemainingArguments)]
  [string[]] $Pattern
)

# ...

# Simply pass $Pattern - containing all patterns -
# directly to Select-String&#39;s -Pattern parameter
select-string -path &quot;c:\temp\lsfile$timestr.txt&quot; -pattern $Pattern | 
  out-file -filepath &quot;c:\temp$param1$timestr.txt&quot;

While more complex than not declaring parameters, this form of your script has the following advantages:

  • It enforces passing at least one pattern.

  • It leaves you free to declare additional parameters, such as optional [switch] parameters (flags), say -CaseSensitive, which won't interfere with collecting the patterns via positional arguments.

  • Through use of a [Parameter()] attribute, your script is now an advanced one, which provides automatic support for common parameters such as -Verbose or -OutVariable.

答案2

得分: 0

数组在PowerShell中很简单 - 要将参数声明为字符串数组,将其类型设置为[string[]]

param(
  [string[]]$SearchTerms
)

现在您只需要针对调用者提供的每个术语重复字符串匹配操作 - 我们可以使用-match运算符针对相关属性,而不是调用Select-String

param(
  [string[]]$SearchTerms
)

$items = Get-ChildItem -path &quot;C:\www\root\*html&quot; -Name |Sort-Object LastWriteTime -Descending

foreach($term in $SearchTerms){
  $items = $items |Where-Object { $_.Name -match $term }
}

return $items
英文:

Array's are pretty simple in PowerShell - to declare a parameter as a string-array, type it [string[]]:

param(
  [string[]]$SearchTerms
)

Now you just need to repeat the string matching operation for each term supplied by the caller - we can use the -match operator against the relevant property rather than invoking Select-String:

param(
  [string[]]$SearchTerms
)

$items = Get-ChildItem -path &quot;C:\www\root\*html&quot; -Name |Sort-Object LastWriteTime -Descending

foreach($term in $SearchTerms){
  $items = $items |Where-Object { $_.Name -match $term }
}

return $items

huangapple
  • 本文由 发表于 2023年5月10日 23:31:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76220254.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定