Search code examples
powershellloopsselect-string

select-string use in a for-each loop


I am working on building a script that will access a large amount of files and pull a specific string out in order to assign as a variable.

The string is similar across all files which is not the issue. I am able to make this process function as an individual event (define a single source file)

$hostname_import = select-string .\test.txt -Pattern 'hostname ABC-.+'
$hostname = $hostname_import -replace '.+ ',''

The above would output the specific hostname identified in the target file (the second function is to trim the word hostname and the space) I can then use this to proceed with using the variable as needed to perform various actions.

The issue I am having is executing this function in a foreach loop so that I can grab the initial file- execute the select-string function (or similar) and then perform the data modification within the loop as expected.

For context- I am going through configuration files- and building a separate file based on those configurations to report on findings- part of the report building requires the hostname of the device.

--EDIT 1: After consulting with my rubber duck I will be attempting to import these files as CSVs in order to potentially reach a solution.

  • Thanks @Otter for getting me through that. I had a previous script that used this function but for whatever reason I couldn't execute the function as anticipated. The difference between my previous script and this one was the $_.Fullname

Huge help!


Solution

  • Select-String can directly process multiple files via its -Path or -LiteralPath parameter, as an array of paths and/or as wildcard expressions.

    What it doesn't support is passing a directory path in order to process the files in it (let alone recursively), so for that you'll have pipe the results of a Get-ChildItem (possibly with -Recurse) to the Select-String call.

    The following example uses the latter technique, looping over all *.config files in the current directory's subtree:

    Get-ChildItem -File -Recurse -Filter *.config |
      Select-String -Pattern 'hostname ABC-(.+)' |
       ForEach-Object {
         $sourceFilePath = $_.Path
         $hostName = $_.Matches[0].Groups[1].Value
       }
    

    Note the use of a capture group ((...)) inside the regex pattern, which allows extracting only the substring of interest from the overall match, via the Microsoft.PowerShell.Commands.MatchInfo instances that Select-String outputs. This obviates the need for the -replace operation; see the bottom section for details.

    Note that multiple matches may be reported per file; if you know that there's only one (or are only interested in the first), add -List to the Select-String call to speed up the operation.


    How to extract only the text (string) of a matching line / line part:

    When you use Select-String output objects (which are of type Microsoft.PowerShell.Commands.MatchInfo) directly in string contexts such as -replace, the resulting string representation contains more than just the line text if a file argument was given, because the input file path is prepended to the line text, followed by :; e.g.:
    C:\path\to\file.config:hostname ABC-foo

    To extract the line text only, directly as a string, use the .Line property.

    • Note: In PowerShell (Core) 7+ you can now ask Select-String to output strings (matching lines) directly, by passing the -Raw switch.

    To extract only the part of the line that the regex matched, use the .Matches property (n/a if the -SimpleMatch switch for literal substring matching was also passed), as shown above.

    • .Matches is a collection of System.Text.RegularExpressions.Match instances (there can only be multiple elements if the -AllMatches switch was passed), the .Value property of each contains the text that matched the pattern overall.

    • If the -Pattern regex contains capture groups ((...)), each Match instance's .Groups collection - itself composed of Match instances - contains what these groups captured, starting with index 1; again the .Value property contains the captured text.