Say I have a config file that looks like this:
{name1}
Settinga=1
settingb=2
settingc=3
{name2}
Settinga=1
settingb=2
settingc=3
{name3}
Settinga=1
settingb=2
settingc=3
I want to be able to change the line settingb=2 under {name3} to another value such as settingb=4
It would be a file store on a Windows OS so ideally it would be done under PowerShell or batch command.
Anyone have any ideas or if this is possible?
Thanks
What you could do read your config file using Get-Content
, and store the section content under each name in a nested hash table, where the name lines are outer keys, and the settings lines are split into keys and values of an inner hash table. To maintain order of keys found from the original file, we can make use of System.Collections.Specialized.OrderedDictionary
. To create one, simply add the [ordered]
attribute to a hashtable @{}
. You can find out more at about_Hash_Tables
.
We can also use System.String.Split
to split the lines by =
, which will use the length to determine if the line is a name or a setting. Length of 1 is a name and length of 2 is a setting.
# Read lines from config file
$config = Get-Content -Path .\config.txt
# Use an ordered hashtable to remember order of keys inserted
$sections = [ordered]@{}
# Keep a key which indicates the current name being added
$currentKey = $null
# Go through each line in the config file
foreach ($line in $config) {
# Split each line by '='
$items = $line.Split("=")
# If splitted items is only one value, we found a new name
# Set the new name and create an inner settings dictionary
if ($items.Length -eq 1) {
$currentKey = $line
$sections[$currentKey] = [ordered]@{}
}
# Otherwise we found a normal line
else {
# Only add the setting if the current name is not null
if ($null -ne $currentKey) {
$sections[$currentKey][$items[0]] = $items[1]
}
}
}
Which will give a hash table $sections
that looks like the following:
Name Value
---- -----
{name1} {Settinga, settingb, settingc}
{name2} {Settinga, settingb, settingc}
{name3} {Settinga, settingb, settingc}
Then you could set a value(or multiple values) like this:
$sections["{name3}"].settingb = 4
And write the updated hash table to a output file using Out-File
. To iterate the outer and inner hash tables, we need to iterate their key value pairs with System.Collections.Hashtable.GetEnumerator
.
& {
# Output each outer key first, where the names are stored
foreach ($outerKvp in $sections.GetEnumerator()) {
$outerKvp.Key
# Then output each setting and value
foreach ($innerKvp in $outerKvp.Value.GetEnumerator()) {
"$($innerKvp.Key)=$($innerKvp.Value)"
}
}
# Pipe output from script block to output file
} | Out-File -FilePath .\output.txt
The above wraps the foreach
loops inside a Call Operator &
to run the script block and pipe the output to Out-File
. You can have a a look at about_Pipelines
and about_Script_Blocks
for more information.
Since I mentioned pipelines and script blocks, we can also use of Foreach-Object
to pass input down the pipeline. From some initial testing it seems this is slightly slower than the above solution(will need to investigate further with larger inputs). You can have a look at this Runtime of Foreach-Object vs Foreach loop question for the differences between both approaches.
$sections.GetEnumerator() | ForEach-Object {
$_.Key
$_.Value.GetEnumerator() | ForEach-Object {
"$($_.Key)=$($_.Value)"
}
} | Out-File -FilePath .\output.txt
And finally the newly created output file below.
output.txt
{name1}
Settinga=1
settingb=2
settingc=3
{name2}
Settinga=1
settingb=2
settingc=3
{name3}
Settinga=1
settingb=4
settingc=3
Which shows settingb
for {name3}
was updated from 2
from 4
.