72

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.

Community
  • 1
  • 1
simonwo
  • 3,171
  • 2
  • 19
  • 28
  • 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. – Tomasz Gandor Mar 10 '21 at 17:50

4 Answers4

72

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 %{...}.

simonwo
  • 3,171
  • 2
  • 19
  • 28
  • 14
    You can also use `Foreach($x in (expression)){}` which isn't quite like xargs but can be helpful for nested objects. – Jaigene Kang Apr 05 '16 at 14:24
  • 55
    Wow. This sucks. This should be a native operator in powershell. Somewhere in the %{&"foo"} I just feel like giving up. – Warren P Feb 07 '17 at 21:22
  • What does `another-step` do? – René Nyffenegger Dec 03 '20 at 09:54
  • @RenéNyffenegger it’s just meant to be an example of some step that outputs something to be used as a command line argument. – simonwo Dec 03 '20 at 10:02
  • 2
    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. – René Nyffenegger Dec 05 '20 at 09:02
  • 1
    @RenéNyffenegger, While `myapp.exe ...` (without double quotes) works as-is, `"myapp.exe" ...` does not: `&`, the [call operator](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Operators#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" ...`). – mklement0 Aug 14 '22 at 21:31
  • @WarrenP Simonwo complicated their answer enormously for no obvious reason. See [my answer](https://stackoverflow.com/a/75558600/16974218) for an extremely simple solution and clarification of the syntax. – adamency Feb 24 '23 at 15:52
  • @JaigeneKang `%` and `Foreach` are the same, see [explanation](https://stackoverflow.com/a/75558600/16974218) – adamency Feb 24 '23 at 15:53
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
Ian Davis
  • 3,848
  • 1
  • 24
  • 30
  • 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. – simonwo May 11 '21 at 20:39
  • @simonwo 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. – Ian Davis May 12 '21 at 21:23
5

I hade issue like this before

In PowerShell you can use:

docker ps -aq | ForEach-Object { docker rm $_ }
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.

radrow
  • 6,419
  • 4
  • 26
  • 53