Doing Something to Each: ForEach-Object

ForEach-Object runs a script block once for every object the pipeline hands it, with $_ as this item, so one line can rename 200 photos, and -WhatIf goes inside the braces, on the command that makes the change, so you can rehearse first.

Apprentice · PowerShell · Operating System · Updated

After this lesson you can

  • Run a command for each object with ForEach-Object, using $_ for this item
  • Rehearse a change to many files with -WhatIf inside ForEach-Object's braces before running it
  • Choose Where-Object, Sort-Object, Measure-Object, Group-Object or ForEach-Object for a job

Key takeaways

  • ForEach-Object { … } runs the code in the braces once for each object that arrives. Inside, $_ is this item.
  • One line renames 200 files: ForEach-Object { Rename-Item $_ $_.Name.Replace('IMG_', 'beach-day-') }.
  • Put -WhatIf inside the braces, on the command that changes things. Rehearse on a few with Select-Object -First 3, then run it on all.
  • Commands such as Remove-Item take the pipe themselves. ForEach-Object is for when each item needs its own work, like a new name made from its old one.

Your camera named every photo from the beach trip IMG_1.jpg, IMG_2.jpg, all the way to IMG_200.jpg, and you'd like them called beach-day-1.jpg and so on, so you can find them next year. Renaming them by hand is 200 rounds of click, F2, type, Enter. File Explorer can rename a whole selection, but only to its own pattern, beach-day (1).jpg. A photographer renaming a shoot, an office assistant renaming a month of scans and a web developer renaming images for a site all have this job, and PowerShell does it in one line.

The same steps for each one: ForEach-Object

A teacher grading a stack of tests does the same steps to each one: take the top test, mark it, put it on the done pile, take the next. ForEach-Object works through a pipeline's objects the same way. It runs the script block in its braces once for each object that arrives, and inside, $_ is this item, as in Filtering: Where-Object and Comparisons:

1..3 | ForEach-Object { "Photo number $_" }
Photo number 1
Photo number 2
Photo number 3

1..3 (read "one to three") hands on the numbers 1, 2 and 3. Inside double quotes, $_ is replaced by the item, so each pass makes a different line.

The same trick makes 200 practice photos. These lines make a camera folder, go into it, and make IMG_1.jpg to IMG_200.jpg, all empty. Running them again is safe: -Force makes any photo that's already there again, empty:

New-Item -ItemType Directory -Force -Path "$HOME\terminal-practice\camera" | Out-Null
Set-Location "$HOME\terminal-practice\camera"
1..200 | ForEach-Object { New-Item "IMG_$_.jpg" -Force } | Out-Null
Get-ChildItem -Name | Select-Object -First 4
IMG_1.jpg
IMG_10.jpg
IMG_100.jpg
IMG_101.jpg

The names sort as text, so IMG_10.jpg comes before IMG_2.jpg, as in Sorting: Sort-Object.

Rehearse on three, then rename all 200

Each photo's new name is made from its old one. .Replace('IMG_', 'beach-day-') is a method, from Properties and Methods: it gives back the name with one piece of text swapped for another. Rename-Item takes the old name, then the new one, as in Renaming: Rename-Item. Rehearse on the first three with -WhatIf:

Get-ChildItem IMG_*.jpg | Select-Object -First 3 | ForEach-Object { Rename-Item $_ $_.Name.Replace('IMG_', 'beach-day-') -WhatIf }
What if: Performing the operation "Rename File" on target "Item: C:\Users\Sam\terminal-practice\camera\IMG_1.jpg Destination: C:\Users\Sam\terminal-practice\camera\beach-day-1.jpg".
What if: Performing the operation "Rename File" on target "Item: C:\Users\Sam\terminal-practice\camera\IMG_10.jpg Destination: C:\Users\Sam\terminal-practice\camera\beach-day-10.jpg".
What if: Performing the operation "Rename File" on target "Item: C:\Users\Sam\terminal-practice\camera\IMG_100.jpg Destination: C:\Users\Sam\terminal-practice\camera\beach-day-100.jpg".

Read it aloud: "for each photo, rename this item to its name with IMG_ replaced by beach-day-, what if." The Destinations look right, so take out Select-Object -First 3 and -WhatIf and run it for real:

Get-ChildItem IMG_*.jpg | ForEach-Object { Rename-Item $_ $_.Name.Replace('IMG_', 'beach-day-') }
Get-ChildItem -Name | Select-Object -First 4
beach-day-1.jpg
beach-day-10.jpg
beach-day-100.jpg
beach-day-101.jpg

Two hundred photos, renamed. The rename prints nothing; the second line shows the first four new names.

-WhatIf goes inside the braces, on Rename-Item, because Rename-Item is what changes things. Put it at the end, outside, and nothing runs at all:

Get-ChildItem IMG_*.jpg | ForEach-Object { Rename-Item $_ $_.Name.Replace('IMG_', 'beach-day-') } -WhatIf
ForEach-Object: The -WhatIf and -Confirm parameters are not supported for script blocks.

When you don't need ForEach-Object

Many commands take the pipe themselves, as The Pipeline: | showed: Get-ChildItem *.tmp | Remove-Item removes each file with no ForEach-Object at all. Reach for ForEach-Object when each item needs its own work, like a new name made from its old one. With only a property's name, ForEach-Object Name hands on that one fact of each object, the way Select-Object -ExpandProperty Name does.

The short names are foreach and %, and you'll see % in other people's commands. Windows PowerShell 5.1 runs this lesson the same way. On a Mac or Linux, IMG_*.jpg doesn't match img_1.jpg: capital letters count in file names there.

Which command for the job?

This chapter's commands each do one thing to the objects that arrive. Pick by what you want to happen to them:

You want to… Use Example
keep only some Where-Object Where-Object Length -gt 1MB
put them in order Sort-Object Sort-Object Length -Descending
count or add them up Measure-Object Measure-Object Length -Sum
sort them into piles Group-Object Group-Object Extension
do something to each ForEach-Object ForEach-Object { Rename-Item … }
take a few, or a few columns Select-Object Select-Object -First 10 Name, Length

Try it. Rehearse renaming every beach-day- photo to start with summer- instead, with -WhatIf inside the braces. Then do it for real.

Check yourself

2 questions to try on the lesson page.

So what

ForEach-Object turns "do this to every one of them" into one line. Rehearse inside the braces on a few, read the Destinations, and then let it do the 200.

Key terms

Cheat sheets

Related