可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
For example, I want to show the output on screen and copy it to clipboard.
dir | tee con | clip
The above doesn't work since con
is not recognized as console in file system in PowerShell.
Also there may be the following scenario,
Get-LongLongOutput | tee con | # Monitor the intermediate output Process-LongLongInput | Process-LongLongInput2 | .....
回答1:
You're going to want to collect your pipeline data and send it to the clipboard separately as @Mathias R. Jessen has suggested. The reason for this is that Clip
is actually an external application and not something built into PowerShell that consumes information piped to it without any 'output' aside from the fact that it copies the information onto the clipboard.
dir | tee -var data $data | clip
This (as suggested by @Mathias R. Jessen) will display your content, and also copy it to the clipboard.
Alternately you could use Write-Host
within a ForEach loop to write things to the screen, and still be able to pass them on the pipeline to clip
, but the formatting of it may be undesirable, and most people suggest avoiding the use of write-host
whenever possible. If you wanted to do it that way you could use something like:
dir | ForEach-Object{ Write-Host $_ $_} | Clip
回答2:
I think TheMadTechnician had the right idea for what you want to do. Write-Host
as you pointed out, is not the same thing as letting the host display the object. The cmdlet you're looking for is Out-Host
. So modifying TMT's suggestion:
Get-Process | ForEach-Object { $_ | Out-Host $_ } | clip
The drawback to this is that it will print the headers for every object, instead of combining them, but I believe this is unavoidable because the only way the host is able to do that without stopping the pipeline is to be at the end of it.
Here it is as a function you can call on your own:
# Don't use this terrible name Function MyTeeFine { [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] $object ) process { $object | Out-Host $object } } # In action: Get-Process | MyTeeFine | clip Get-Process | MyTeeFine | ForEach-Object { Start-Sleep -Seconds 1 } # ^ proof that the pipeline is still working object by object, # displaying to host before each delay
回答3:
For such cases I prefer to use my own Invoke-Pipeline
cmdlet:
Add-Type -TypeDefinition @‘ using System; using System.Management.Automation; [Cmdlet(VerbsLifecycle.Invoke,"Pipeline")] public sealed class InvokePipelineCmdlet:PSCmdlet,IDisposable { private static readonly Type StopUpstreamCommandsExceptionType=typeof(FlowControlException).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"); private ScriptBlock[] scriptBlocks; private PSObject inputObject; private int count; private SteppablePipeline[] steppablePipelines; public InvokePipelineCmdlet() { } [Parameter(Mandatory=true,ValueFromRemainingArguments=true)] public ScriptBlock[] Pipeline { set { scriptBlocks=value; } } [Parameter(ValueFromPipeline=true)] public PSObject InputObject { get { return null; } set { inputObject=value; } } protected override void BeginProcessing() { count=scriptBlocks.Length; steppablePipelines=new SteppablePipeline[count]; for(int i=0;i<count;++i) { steppablePipelines[i]=scriptBlocks[i].GetSteppablePipeline(MyInvocation.CommandOrigin); } ProcessPipelineStep(PipelineStep.Begin); } protected override void ProcessRecord() { ProcessPipelineStep(MyInvocation.ExpectingInput?PipelineStep.ProcessInput:PipelineStep.Process); } protected override void EndProcessing() { ProcessPipelineStep(PipelineStep.End); Dispose(); } public void Dispose() { if(steppablePipelines!=null) { foreach(SteppablePipeline steppablePipeline in steppablePipelines) { if(steppablePipeline!=null) { steppablePipeline.Dispose(); } } steppablePipelines=null; } } private void ProcessPipelineStep(PipelineStep step) { if(steppablePipelines!=null) { for(int i=0;i<steppablePipelines.Length;++i) { if(steppablePipelines[i]!=null) { try { switch(step) { case PipelineStep.Begin: steppablePipelines[i].Begin(this); break; case PipelineStep.Process: steppablePipelines[i].Process(); break; case PipelineStep.ProcessInput: steppablePipelines[i].Process(inputObject); break; case PipelineStep.End: steppablePipelines[i].End(); break; } } catch(FlowControlException fce) { if(StopUpstreamCommandsExceptionType.IsInstanceOfType(fce)) { steppablePipelines[i]=null; if(--count==0) { EndProcessing(); throw (FlowControlException)Activator.CreateInstance(StopUpstreamCommandsExceptionType,this); } } else { throw; } } } } } } private enum PipelineStep { Begin, Process, ProcessInput, End } } ’@ -PassThru|Select-Object -First 1 -ExpandProperty Assembly|Import-Module
It allows to pass single input to multiple commands simultaneously like this:
dir|Invoke-Pipeline {clip} <# copy to clipboard #> ` {Out-File out.txt} <# save to file #> ` {Out-File outall.txt -Append} <# append to some other file #> ` {Out-Host} <# display on console #>
With your example scenario you can do the following:
Get-LongLongOutput | Invoke-Pipeline {Out-Host} {Process-LongLongInput} | Process-LongLongInput2 | .....
Or you can do something like this:
Get-LongLongOutput | Invoke-Pipeline {Out-GridView} {Process-LongLongInput} | Invoke-Pipeline {Out-GridView} {Process-LongLongInput2} | .....
so you can monitor output in multiple points.