Dominic Brunetti
Dominic Brunetti

Reputation: 1069

Using regex in a key/value lookup table in powershell?

I am creating the below script to search through and replace data in a set of files. The problem I'm running into is I need to ONLY match if it's the beginning of the line, and I'm not sure how/where would I use regex in the below example (e.g. ^A, ^B) when doing the comparison? I tried putting the caret in front of the name values in the table, but that didn't work...

$lookupTable = @{
    'A'='1';
    'B'='2'
    #etc
}

Get-ChildItem 'c:\windows\system32\dns' -Filter *.dns | 
Foreach-Object {
    $file = $_
    Write-Host "$file"
    (Get-Content -Path $file -Raw) | ForEach-Object {
        $line = $_
        $lookupTable.GetEnumerator() | ForEach-Object {
            $line = $line -replace $_.Name, $_.Value
        }
        $line
    } | Set-Content -Path $file
}

Upvotes: 0

Views: 1057

Answers (3)

user2674513
user2674513

Reputation:

# Here is a complete, working script that beginners can read. 

# This thread
# Using regex in a key/value lookup table in powershell?
# https://stackoverflow.com/questions/57277282/using-regex-in-a-key-value-lookup-table-in-powershell


# User-modifiable variables. 

# substitutions
# We need to specify what we're looking for (keys). 
# We need to specify our substitutions (values). 
# Example: Looking for A and substituting 1 in its place. 
# Add as many pairs as you like. 
# Here I use an array of objects instead of a Hashtable so that I can specify upper- and lowercase matches. 
# Use the regular expression caret (^) to match the beginning of a line. 
$substitutions = @(
    [PSCustomObject]@{ Key = '^A';    Value = '1'                 },
    [PSCustomObject]@{ Key = '^B';    Value = '2'                 },
    [PSCustomObject]@{ Key = '^Sit';  Value = '[Replaced Text]'   }, # Example for my Latin placeholder text. 
    [PSCustomObject]@{ Key = 'nihil'; Value = '[replaced text 2]' }, # Lowercase example. 
    [PSCustomObject]@{ Key = 'Nihil'; Value = '[Replaced Text 3]' }  # Omit comma for the last array item. 
)


# Folder where we are looking for files. 
$inputFolder = 'C:\Users\Michael\PowerShell\Using regex in a key value lookup table in powershell\input'
# Here I've created some sample files using Latin placeholder text from
# https://lipsum.com/


# Folder where we are saving the modified files. 
# This can be the same as the input folder. 
# I'm creating this so we can test without corrupting the original files. 
$outputFolder = 'C:\Users\Michael\PowerShell\Using regex in a key value lookup table in powershell\output'
#$outputFolder = $inputFolder


# We are only interested in files ending with .dns  
$filterString = '*.dns'
# Here is an example for text files. 
#$filterString = '*.txt'
# For all files. 
#$filterString = '*.*'
# More info. 
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-childitem?view=powershell-6#parameters
# Search on the page for -Filter  


# You won't need to update any variables after this line. 
# ===================================================================

# Generate a list of files to look at. 
$fileList = Get-ChildItem $inputFolder -Filter $filterString


# Simple example. 
# get-content .\apple.dns | % { $_ -replace "sit", "michael" } | set-content "C:\output\apple.dns"
#             input file        substitutions                    output

# Set up loops. 

# For each file. 
#{
#    For each key-value pair. 
#}


# "For each key-value pair."
# Create a function. 
# Pipe in a string. 
# Specify a list of substitutions. 
# Make the substitutions. 
# Output a modified string. 
filter find_and_replace ([object[]] $substitutions)
{
    # The automatic variable $_ will be a line from the file. 
    # This comes from the pipeline. 

    # Copy the input string. 
    # This avoids modifying a pipeline object. 
    $myString = $_

    # Look at each key-value pair passed to the function. 
    # In practice, these are the ones we defined at the top of the script. 
    foreach ($pair in $substitutions)
    {  
        # Modify the strings. 
        # Update the string after each search. 
        # case-sensitive -creplace instead of -replace
        $myString = $myString -creplace $pair.Key, $pair.Value
    }

    # Output the final, modified string. 
    $myString
}




# "For each file."
# main
# Do something with each file. 
foreach ($file in $fileList)
{
    # Where are we saving the output? 
    $outputFile = Join-Path -Path $outputFolder -ChildPath $file.Name

    # Create a pipeline. 
    # Pipe strings to our function. 
    # Let the function modify the strings. 
    # Save the output to the output folder. 
    # This mirrors our simple example but with dynamic files and substitutions. 
    # find_and_replace receives strings from the pipeline and we pass $substitutions into it. 
    Get-Content $file | find_and_replace $substitutions | Set-Content $outputFile


    # The problem with piping files into a pipeline is that 
    # by the time the pipeline gets to Set-Content, 
    # we only have modified strings
    # and we have no information to create the path for an output file. 
    # ex [System.IO.FileInfo[]] | [String[]] | [String] | Set-Content ?
    # 
    # Instead, we're in a loop that preserves context. 
    # And we have the opportunity to create and use the variable $outputFile  
    # ex foreach ($file in [System.IO.FileInfo[]])
    # ex     $outputFile = ... $file ...
    # ex     [String[]] | [String] | Set-Content $outputFile


    # Quote 
    # (Get-Content -Path $file -Raw)
    # By omitting -Raw, we get:   one string for each line. 
    # This is instead of getting: one string for the whole file. 
    # This keeps us from having to use 
    # the .NET regular expression multiline option (and the subexpression \r?$) 
    # while matching. 
    # 
    # What it is. 
    #   Multiline Mode
    #     https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options#Multiline
    #
    # How you would get started. 
    #   Miscellaneous Constructs in Regular Expressions
    #     https://learn.microsoft.com/en-us/dotnet/standard/base-types/miscellaneous-constructs-in-regular-expressions
}

Upvotes: 1

vrdse
vrdse

Reputation: 3164

The -replace operator accepts Regex. Just $line = $line -replace "^$($_.Name)", "$_.Value".

Upvotes: 1

Lee_Dailey
Lee_Dailey

Reputation: 7489

the way that regex works makes getting a proper "start of line" marker into the regex pattern along with the $VarName a tad iffy. so i broke it out into it's own line and used the -f string format operator to build the regex pattern.

then i used the way that -replace works on an array of strings that one usually gets from Get-Content to work on the whole array at each pass.

note that the strings have lower case items where they otta be replaced, and uppercase items where the item should NOT be replaced. [grin]

$LookUpTable = @{
    A = 'Wizbang Shadooby'
    Z = '666 is the number of the beast'
    }

$LineList = @(
    'a sdfq A er Z xcv'
    'qwertyuiop A'
    'z xcvbnm'
    'z A xcvbnm'
    'qwertyuiop Z'
    )
$LookUpTable.GetEnumerator() |
    ForEach-Object {
        $Target = '^{0}' -f $_.Name
        $LineList = $LineList -replace $Target, $_.Value
        }

$LineList

output ...

Wizbang Shadooby sdfq A er Z xcv
qwertyuiop A
666 is the number of the beast xcvbnm
666 is the number of the beast A xcvbnm
qwertyuiop Z

Upvotes: 1

Related Questions