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.