I\'m looking for a feature comparable to Python interactive shell\'s \"_\" variable. In PowerShell I want something like this:
> Get-Something # this returns
No there is not automatic variable like that.
You have to do:
$output = Get-Something
$output
$anObj = $output
to get the behaviour
Last option that requires most work but IMO gives you what you ask for: create proxy that will overwrite Out-Default (that is always called implicitly at the end of pipeline, if you don't out-* to something else).
Jeffrey Snover gave presentation on it during one of PowerShell Deep Dives (I reckon it was the first one) - you can find scripts he used (including above mentioned out-default) on Dmitry Sotnikov blog. You can also watch video from it to understand whole concept.
As stated, there's no built-in support for this, but here's a simple, but suboptimal PSv3+ custom solution:
Note:
For a proper, but nontrivial solution, see BartekB's helpful answer.
There is a feature request on Github to build this functionality into a future PowerShell Core version (the current version as of this writing is PowerShell [Core] 7.0).
Add the following to your $PROFILE
file:
# Store previous command's output in $__
$PSDefaultParameterValues['Out-Default:OutVariable'] = '__'
What to name the variable - such as $__
(2 underscores) in this example - is up to you, but beware of name collisions, notably with $_
, the automatic variable that represents the input object at hand in a number of contexts.
This will capture the terminal-bound output of the most recently executed PowerShell command [that produced terminal output] in variable $__
in your interactive sessions,
by way of PowerShell's ability to globally preset parameter defaults - see Get-Help about_Parameters_Default_Values.
-OutVariable
is a common parameter designed to collect a cmdlet's / advanced function's output objects in a variable, and the above definition applies this parameter implicitly to all Out-Default
calls, which in turn is called behind the scenes whenever PowerShell outputs something to the terminal - however, note the exceptions mentioned below.
Caveats:
If needed, use $saved = $($__)
to save the captured output for later use, given that $__
is reassigned to on every command (of course, if you know ahead of time that you want to keep a command's output, use an assignment to begin with: $saved = <command>
).
$saved = $__
does not work, because that makes $saved
point to the same [ArrayList]
instance as $__
, which gets repopulated on the next command; $(...)
(or @(...)
if you also want a single-object result to be an array) ensures that the array list's elements are collected in a new array.Output is not captured in the following cases:
Output from external programs, such as git
, because by design PowerShell passes the output streams from external programs directly to the terminal (unless they're redirected or captured), and therefore doesn't call Out-Default
. The simplest workaround is to pipe to Write-Output
(something like *>&1
to explicitly route through PowerShell streams doesn't work); e.g.:
whoami.exe | Write-Output # $__ is now populated
Output from commands that explicitly call a formatting cmdlet - Format-Custom
, Format-Hex
, Format-List
, Format-Table
, or Format-Wide
.
$PSDefaultParameterValues['Format-*:OutVariable'] = '__'
, but, unfortunately, this would collect formatting objects (instructions) rather than the original data in $__
, which is undesired.
An unsatisfying workaround is to capture Format-*
output in a different variable, which not only requires you to think about which variable you need to target, but you'll still only see formatting objects rather than data, and, since Format-*
cmdlets are involved behind the scenes even if you don't use them explicitly, the output of commands without Format-*
calls is then captured twice - once as data, in $__
, and again as formatting objects, in the other variable.Due to a design quirk, $__
will always contain an array list (of type [System.Collections.ArrayList]
), even if the previous command output only a single object. When in doubt, use $($__)
(or $__[0]
) to get a single output object as such.
Beware of commands producing very large output sets, because $__
will collect them in memory.
$__
will only capture objects output to the terminal - just like _
does in Python; a command that produces no output or $null
/ an array of $null
s leaves any previous $__
value intact.
This is from Windows Powershell in Action. Source this in your $profile, and it makes $last contain the output of the last command. It also will change directories by typing the name of the directory a la zsh, or go to a website in a browser by typing the url of the website. It makes an Out-Default function that overrides the Out-Default cmdlet and calls it. I added the "start-process $__command" line so going to a website would work in osx.
This is fairly invasive. I later disabled it in my $profile for some reason.
#
# Wrapping an exiting command with a function that uses
# steppable pipelines to "remote-control" the wrapped command.
function Out-Default
{
[CmdletBinding(ConfirmImpact="Medium")]
param(
[Parameter(ValueFromPipeline=$true)]
[System.Management.Automation.PSObject] $InputObject
)
begin {
$wrappedCmdlet = $ExecutionContext.InvokeCommand.GetCmdlet(
"Out-Default")
$sb = { & $wrappedCmdlet @PSBoundParameters }
$__sp = $sb.GetSteppablePipeline()
$__sp.Begin($pscmdlet)
}
process {
$do_process = $true
if ($_ -is [System.Management.Automation.ErrorRecord]) {
if ($_.Exception -is
[System.Management.Automation.CommandNotFoundException]) {
$__command = $_.Exception.CommandName
if (test-path -path $__command -pathtype container) {
set-location $__command
$do_process = $false
}
elseif ($__command -match '^http://|\.(com|org|net|edu)$') {
if ($matches[0] -ne "http://") {$__command = "HTTP://" +
$__command }
# [diagnostics.process]::Start($__command)
start-process $__command
$do_process = $false
}
}
}
if ($do_process) {
$global:LAST = $_;
$__sp.Process($_)
}
}
end {
$__sp.End()
}
}