Reputation: 2655
I'm trying to replicate this command into PowerShell:
grep -lR 'Find' ./src | while read filename; do sed -i".bak" 's/Find/Replace/g' "$filename"; done
What I have so far:
Get-ChildItem "src" -Recurse -File | ForEach-Object { $f = $_; (Get-Content $f) | ForEach-Object { $_ -replace "Find", "Replace" } | Set-Content "$f.tmp"; Move-Item "$f.tmp" $f -Force }
I'm getting an error saying "filename.tmp" does not exist
. I thought the above would create the file while parsing. Any help would be appreciated.
Upvotes: 2
Views: 1556
Reputation: 439682
Most likely you've fallen victim to Windows PowerShell's inconsistent stringification of the System.IO.FileInfo
instances output by Get-ChildItem
- see this answer.
The workaround is to use explicit stringification via the .FullName
property, which explicitly returns an item's full path.
Applied to your command, alongside some optimizations:
Get-ChildItem -File -Recurse src | ForEach-Object {
$f = $_.FullName # !! Explicitly retrieve the full path
(Get-Content $f -Raw) -creplace 'Find', 'Replace' |
Set-Content -NoNewline "$f.tmp"
Move-Item "$f.tmp" $f -Force
}
Get-Content -Raw
reads the entire file into memory as a single string, which is more efficient.
-creplace
(which performs case-sensitive replacement, as sed
would by default) is directly applied to the resulting multiline string and replaces all occurrences.
-NoNewline
(PSv5+) ensures that Set-Content
doesn't add an additional trailing newline to the multiline string being saved (the same applies to Out-File
/ >
).
Note: Given that Get-Content -Raw
reads the entire file up front, you could even write the modified content back to the very same file, without requiring an intermediate temporary file and a subsequent Move-Item
call; that said, doing so bears a slight risk of data loss, if the process of writing back to the same file is interrupted.
sed
call retains the original file with extension .bak
, your PowerShell command does not.Upvotes: 2