1

我有许多我经常使用的 sed/perl/etc “单行”命令:

  • head -1(打印文件的第一行)
  • sed $d(删除文件的最后一行)
  • perl -pe '$_ = qq($. $_)'(文件中的数字行)

你明白了。

所有这些命令都有相同的行为——它们可以从标准输入中获取输入,或者处理一系列名称作为参数传递的文件。我想将这些常用脚本包装为 Powershell 函数,这样我就不必记住要使用的确切语法。但是,别名不是那样工作的,如果我用函数做“明显”的方法:

function numlines {
    perl -pe '$_ = qq($. $_)' $args
}

它适用于作为参数的文件 ( numlines my_file.pl),但不适用于来自管道 ( cat my_file.pl | numlines) 的输入。

有没有办法编写函数以使其双向工作?

澄清一下 - 我可以使用 bat 文件来做到这一点。例如, numlines.bat 包含

@perl -pe "$_ = qq($. $_)" %*

但是必须调用 cmd.exe 和 bat 文件的一般丑陋(即“终止批处理作业(Y/N)?”当您按下 CTRL-C :-() 时的提示让我希望在 Powershell 中有一个类似简单的解决方案。 .


根据下面理查德的建议,我尝试了:

function test {
  [CmdletBinding()]

  param(
    [Parameter(mandatory=$true, ValueFromPipeline=$true)]
    $data
  )

  process {
    perl -pe '$_ = qq($. $_)'
  }
}

如果我然后这样做(test file.txt我希望以perl -pe '$_ = qq($. $_)' file.txtfile.txt当我尝试时也会发生同样的事情cat file.txt | test——我希望它的行为与cat file.txt | perl -pe '$_ = qq($. $_)'.

4

2 回答 2

5

基础知识:高级功能可以做的比标准功能更多

有没有办法编写函数以使其双向工作?

是的,使用高级功能,将为每个输入对象调用流程块。

  • 您需要在块[CmdletBinding]之前的开始处指定。param
  • 您需要一个接受管道输入的参数,这是通过Parameter该参数的属性完成的。

像这样:

function ReadInput {
  [CmdletBinding]
  param(
    [Parameter(mandatory=$true, ValueFromPipeline=$true)]
    $data
  )

  process {
    "Input was: $data";
  }
}

更好地在本地做事

head -1(打印文件的第一行)

查看 : 的参数First只会传递第一个对象。Select-Object... | Select -f 1 | ....

sed $d(删除文件的最后一行)

这个更难......本质上是一个跟踪是否有另一行的函数。

perl -pe '$_ = qq($. $_)' (文件中的行数)

您需要Measure-Object,如果没有其他参数,它将计算它在管道上接收到的对象数。


为什么它不起作用

(基于扩展问题)

这有两个部分:

首先:您需要将绑定到管道的参数的值传递给您的操作。所以:

process {
  perl -pe '$_ = qq($. $_)'
}

应该

process {
  $data | perl -pe '$_ = qq($. $_)'
}

第二:这可能不适用于您的许多实用程序,因为每次process执行块时都会执行管道的一个新实例,包括sed对管道上每个对象的(等)新调用,因此会丢失您通常期望它从一行到下一行保持的任何状态。

这周围有两条路线。首先,您可以使用steppable pipelines,这是一个高级主题(唯一体面的介绍是在 Bruce Payette 的Windows PowerShell in Action第二版一书中(他完成了大部分 PSH 语言的设计和实现))。

第二:做事原生。例如。文件中的行数(不使用Measure-Object):

function Get-ObjectCount {
  [CmdletBinding]
  param(
    [Parameter(mandatory=$true, ValueFromPipeline=$true)]
    [object[]]$data   # Accept an array...
  )

  begin {
    $count = 0  # Not strictly needed: PSH will default this.
  }

  process {
    $count += $data.length
  }

  end {
    $count
  }
}

这也会快得多(无需创建另一个进程。

只要您专注于将 PSH 作为包装器,您就会发现您正在两全其美:失去 *ix 类型工具的灵活性(PSH 的执行模型不同:在一个进程中协作工具)失去PSH 的灵活性(PSH 适用于类型化对象而不是字符串)。

于 2013-01-30T15:38:30.143 回答
0

看来解决方案的关键是在函数中同时使用 $args(用于命令行参数)和 $input(用于管道输入),如下所示:

function wrapper {
    $input | WRAPPED_COMMAND_HERE $args
}

因此,例如,perl 命令对行数的情况如下所示

PS> function nl {
>>    $input | perl -pe '$_ = qq($. $_)' $args
>>  }
>>
PS> nl test.txt
1 This is some
2 test data
PS> type test.txt | nl
1 This is some
2 test data
于 2013-02-01T10:44:37.670 回答