What's the equivalent of xargs in PowerShell?
Asked Answered
C

4

80

The POSIX-defined xargs command takes all of the items it receives from standard input and passes them as command-line arguments to the command it receives on it's own command line. E.g: grep -rn "String" | xargs rm.

What's the equivalent in PowerShell?

The following questions all ask this:

but there is no correct answer because all the answers either use ForEach-Object, which will process items one-at-a-time (like xargs -n1) which gets the desired result for the examples given, or store the intermediate result in a variable, which offends my functional commandline-fu.

Cheeky answered 5/4, 2016 at 14:15 Comment(1)
Well, xargs -n1 is also useful. As is find ... | while read f ; do ... done... And somehow doing it in PS by intuition is virtually impossible.Hinder
C
76

There are two ways that I've found. The first is probably more idiomatic PowerShell, and the second is more true to the pipe-based spirit of xargs.

As an example, let's say we want to pass all our cat pics to myapp.exe.

Method #1: Command substitution

You can do something similar to using $(command substitution) in sh by embedding your pipeline in the command string:

&"myapp.exe" @(Get-ChildItem -Recurse -Filter *.jpg | Another-Step)

The @(...) creates an array from the command inside it, and PowerShell automatically expands arrays passed to & into seperate command-line parameters.

However, this does not really answer the question, because it will only work if you have control over the command you want to pass to, which may not be the case.

Method #2: True piping

You can also construct a "double pipeline" by having a sub-expression to pipe your objects, collecting them to an array, and then piping the array to your final command.

,@(Get-ChildItem -Recurse -Filter *.jpg | Another-Step) | %{&"myapp.exe" $_}

The @(...) as before collects the items into an array, and the array is then piped to the final command which is invoked using % (ForEach-Object). Ordinarily, this would then loop over each item individually, because PowerShell will automatically flatten the array when it's piped, but this can be avoided by prepending the , operator. The $_ special variable is then used as normal to embed the passed array.

So the key is to wrap the pipeline you want to collect in ,@(...), and then pipe that to something in %{...}.

Cheeky answered 5/4, 2016 at 14:15 Comment(8)
You can also use Foreach($x in (expression)){} which isn't quite like xargs but can be helpful for nested objects.Cotillion
Wow. This sucks. This should be a native operator in powershell. Somewhere in the %{&"foo"} I just feel like giving up.Milstone
What does another-step do?Overwrought
@RenéNyffenegger it’s just meant to be an example of some step that outputs something to be used as a command line argument.Cheeky
I have another question: you write PowerShell automatically expands arrays passed to & into seperate command-line parameters. As far as I can see on PowerShell 7, this is the behavior of executing executables, regardless if they're executed using & or without ("myapp.exe" @(Get-ChildItem -Recurse -Filter *.jpg | Another-Step) also works.Overwrought
@RenéNyffenegger, While myapp.exe ... (without double quotes) works as-is, "myapp.exe" ... does not: &, the call operator, always works, but is only required if the name/path is quoted and/or contains variable references. When it is used, it's better to put a space between & and its operand, for conceptual clarity and for symmetry with . , the dot-sourcing operator (& "myapp.exe" ...).For
@WarrenP Simonwo complicated their answer enormously for no obvious reason. See my answer for an extremely simple solution and clarification of the syntax.Expert
@JaigeneKang % and Foreach are the same, see explanationExpert
P
10

I hade issue like this before

In PowerShell you can use:

docker ps -aq | ForEach-Object { docker rm $_ }
Pipsissewa answered 2/2, 2023 at 12:39 Comment(0)
F
6

I've been using this filter for the basic xargs execution.

filter xargs { ($h,$t) = $args; & $h ($t + $_) }

which is roughly equivalent to:

filter xargs { & $args[0] ($args[1..$args.length] + $_) }

Examples

 docker ps -q | xargs docker stop

 gem list | % { $_.split()[0] } | xargs gem uninstall -aIx
Fissi answered 11/5, 2021 at 14:55 Comment(2)
I like the syntactic sugar, but don’t filters process items one at a time like xargs -n1 and the linked examples? E.g. ls | echo “hello” appends “hello” to every line of output.Cheeky
@Cheeky You are correct, ls | xargs -n1 echo "hello" will print hello before each item but ls | xargs echo "hello" just prints hello once before all text output. In powershell, using the above, ls | xargs Write-Host "hello " will print hello before each item like the bash -n1 option. To replicate the normal xargs behavior, you can force the object into an array first ,(ls) | xargs Write-Host "hello ". All of my use cases fall into the -n1 pattern. This can be changed to a function and expanded to accept more arguments and behave like normal xargs.Fissi
S
1

I think the closest you can get to is to use Splatting. In this example I use foreach loop to collect all pipelined values to extend unnamed positional parameters:

function Xargs {
    param(
        $Cmd
    )
    process {
        $args += ,$_
    }
    end {
        & $Cmd @args
    }
}

Now test:

PS> '-c','"print(123)"' | Xargs python
123

Note that this doesn't work with named parameters nor flags -- it only passes pure positional parameters, so it should work best with non-cmdlets.

That also means I highly discourage you from trying '-WhatIf' | Xargs Remove-WholeSystem.

Smooth answered 24/6, 2022 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.