Reputation: 13
So I have a whole large folder of text files where I'd like to change the string unamed in each one of them to the name of the file itself, extension excluded.
So far with my scavengous skills I have come up with the following, but it doesn't give what I'm looking for as it changes the name to every single collected file
$path = "*.txt"
$filename = Get-Item $path | Select-Object -ExpandProperty BaseName
$Change = Get-Content $path
$Change | ForEach-Object {$_ -Replace "unamed", $filename} | Set-Content $path
So basically something like this
The name of this file is unamed.
is becoming
The name of this file is File1Name File2Name File3Name.
There is also a side issue I'm experiencing where the results of the all the files merges into one file (so contents of file 1 will now also contain contents of all the other files too). How can I get the results I seek oh wise ones?
If there is a way to settle this with regular command line instead of powershell then I'll settle for that too (I found this but had issue with replace_string being invalid command? Batch script that replaces static string in file with filename)
Upvotes: 1
Views: 916
Reputation: 440471
Esperanto's answer works well and is PSv2-compatible; here's a much faster - but more memory-intensive - PSv3+ solution:
Get-Content -Raw *.txt | ForEach-Object {
$_ -replace 'unamed', [IO.Path]::GetFileNameWithoutExtension($_.PSPath) |
Set-Content $_.PSPath
}
Unless you have special file-matching needs such as looking for files recursively or needing to exclude files or needing to match by file attributes, you don't need Get-Item
/ Get-ChildItem
to enumerate files of interest - you can pass a wildcard pattern such as *.txt
directly to Get-Content
's (implied) -Path
parameter.
Get-Content -Raw
(PSv3+) reads the the entire content of each file matching *.txt
into memory as a whole, as a single string.
Get-Content
decorates the strings it outputs with information about the where the string came from, so that the .PSPath
property on each string returned contains the full filename of the file the string was read from.
[IO.Path]::GetFileNameWithoutExtension($_.PSPath)
extracts the filename root from the path (the mere filename without extension)
Split-Path
cmdlet doesn't allow you to do this in Windows PowerShell, but in PowerShell Core you could use Split-Path -LeafBase
instead.Caveat: Set-Content
uses a default encoding (Windows PowerShell: "ANSI"; PowerShell Core: UTF-8 without BOM), which may or may not be the same as the input encoding; use the -Encoding
parameter as needed.
As for what you tried:
$filename = Get-Item $path | Select-Object -ExpandProperty BaseName
$filename
now contains an array of filename roots (base names).
$Change = Get-Content $path
$Change
now contains a single array whose elements are the lines from all input files.
$Change | ForEach-Object {$_ -Replace "unamed", $filename} | Set-Content $path
Because $Change
is a single, flat array, you've lost the partitioning of the array elements into the files or origin, and Set-Content
sends all lines to the target file(s).
Because $filename
is an array whereas the 2nd RHS operand of -replace
only supports a scalar (single string), $_ -replace "unamed", $filename
causes $filename
to be converted to a single string, which is a space-separated concatenation of the array elements (filename roots).
Set-Content $Path
so happens to write the flat (modified) array of lines to multiple files, namely those existing files that match the wildcard expression in $Path
, *.txt
:
As stated, the input Set-Content
receives is a concatenation of the input files' lines, so all output files receive the concatenated (modified) content from all input files (all files whose names match wildcard pattern *.txt
)
As an aside: That Set-Content *.txt
allows writing the same content to multiple preexisting output files is a surprising and possibly destructive feature and should arguably not be supported - see this GitHub discussion.
Upvotes: 0
Reputation: 17492
Something like this?
Get-ChildItem -Filter "*.txt" | ForEach-Object {
$Path=$_.FullName; (Get-Content $Path) -replace 'unamed', $_.BaseName | Set-Content $Path
}
Upvotes: 2