Milk
Milk

Reputation: 2655

Grep | sed -i equivalent for Windows

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

Answers (1)

mklement0
mklement0

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.

    • Also, while your sed call retains the original file with extension .bak, your PowerShell command does not.

Upvotes: 2

Related Questions