3

A welcome-to-PowerShell guide has the PowerShell equivalents of the commands pwd, cd, ls, find, cp, rm, mkdir, touch, cat, tail, grep, uname, mkfs, ping, man, and cut: https://mathieubuisson.github.io/powershell-linux-bash/

In addition, one can find additional equivalences of other command-line commands, such as which, where, alias, wget, curl, type, %0, and even command overriding.

However, I am still having trouble figuring out the way to do a simple for statement in Powershell. For example, the following would group all files based on filename, assuming every filename has a .txt equivalent:

 set b=0
 for %a in (*.txt) do set /a b=!b!+1 > NUL & echo (!b!) & dir /b %~na.*

What is the equivalent in PowerShell? The command

ls *.txt | ForEach-Object {Split-Path -LeafBase -Path $_.Name}

lists the files. However, how do I pass the filename into the dir /b %~na.* command? It seems that as soon as I close the braces for the For-Each-Object command, then the results want to pass as an agglomeration, rather than being able to process them one line at a time. And if I add another layer around Split-Path, then the $_ ends up being a different object, so it's not able to split off the filename from the extension.

EDIT: Is there a better way for me to parse results one at a time? The for command was suggested in the comments; I just have a hard time figuring out how to get it to process all the *.txt file; but if that were doable, maybe that would be the way to go?

The preference is to do this from command line, without needing to write to a file, if possible.

Alex
  • 250

1 Answers1

5

PowerShell's version of for is generic, closer to programming languages – it doesn't loop over files specifically, but over arbitrary lists, meaning that you need another command to produce a list of files.

(This is one thing to unlearn when going from Cmd to anything else – sometimes there are no equivalences because the entire architecture is different.)

dir aka gci aka Get-ChildItem works for this purpose – even though running it alone displays a fancy table, in reality the result is an object that only becomes text when shown:

$files = Get-ChildItem *.txt

echo $files[0]

Pipe it into ForEach-Object to get the loop:

Get-ChildItem *.txt | ForEach-Object { echo $_ }

$files | ForEach-Object { ... }

dir *.txt | % { echo $_ }

The current iteration value is always $_ although you can assign it to a custom variable inside the block.

Another way to write the loop is to use a more traditional foreach {} block like in many programming languages:

foreach ($item in (dir *.txt)) {
    echo $item
}

foreach ($item in $files) {...}


In this case, Get-ChildItem doesn't just return a list of file names; it returns objects with properties. For example, $_.FullName contains the file's entire path (and which you can use with Split-Path if needed) while $_.Name is just the name alone.

$files[0] | format-list *

The equivalent of %~na is likewise generic and not in any way dependent on the for loop.

As with any other PowerShell command, just running it alone will display results in stdout (like you have in your own code), but you can assign them to a variable:

$base = Split-Path -LeafBase $path

which can then be done as part of a loop:

$count = 0
Get-ChildItem *.txt | ForEach-Object {
    $count += 1
    $base = Split-Path -LeafBase $_.Name
    echo "looking for $base ($count)"
    Get-ChildItem "$base.*"
}

Similarly, any command can be directly used as a substitution like variables can, using $(...), if you want it all in one line:

dir *.txt | % { dir "$(split-path -leafbase $_.Name).*" }
grawity
  • 501,077