英文:
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的参数绑定器会自动强制要求给定的参数(值)是该类型或可以转换为它。
- 声明的参数可以被声明为特定的.NET数据类型,比如
-
使用 命名 参数:
- 声明的参数可以以更具描述性的方式绑定(传递参数给它),通过在参数名称之前添加参数的名称;例如,如果您定义了一个参数
$Pattern
,那么参数foo
可以以-Pattern foo
的方式传递给它,而不仅仅是foo
,后者是一个 位置 参数,其相对于其他类似参数的位置决定了它绑定到哪个参数。
- 声明的参数可以以更具描述性的方式绑定(传递参数给它),通过在参数名称之前添加参数的名称;例如,如果您定义了一个参数
-
健壮性:
PowerShell使得声明 数组 参数变得容易 - 例如 [string[]] $Pattern
声明一个字符串数组。
-
默认情况下,它们也需要一个 数组 作为它们的参数,这意味着在调用时一个 单一的参数,其数组元素用
,
分隔 - 例如,要传递模式foo
和bar
,您必须使用以下方式: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"${@:1:1}"
),$args[1]
the second (compare with"${@:2:1}"
), 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's -Pattern parameter
select-string -path "c:\temp\lsfile$timestr.txt" -pattern $args |
out-file -filepath "c:\temp$param1$timestr.txt"
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 singleSelect-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.
- Declared parameters can be declared as a specific .NET data type, such as
-
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 argumentfoo
can be passed to it as-Pattern foo
instead of justfoo
, the latter being a positional argument, whose position relative to other such arguments determines what parameter it binds to.
- 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
-
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 yourparam(...)
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 patternsfoo
andbar
, 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's -Pattern parameter
select-string -path "c:\temp\lsfile$timestr.txt" -pattern $Pattern |
out-file -filepath "c:\temp$param1$timestr.txt"
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 "C:\www\root\*html" -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 "C:\www\root\*html" -Name |Sort-Object LastWriteTime -Descending
foreach($term in $SearchTerms){
$items = $items |Where-Object { $_.Name -match $term }
}
return $items
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论