# Search for text then return text inbetween braces

**URL:** <https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371>\
**Category:** PowerShell Help\
**Created:** [August 21, 2021, 5:09pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371 "2021-08-21T17:09:37Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![vegarjb](https://avatars.discourse-cdn.com/v4/letter/v/ecb155/32.png) [@vegarjb](https://forums.powershell.org/u/vegarjb)\
**Post date:** [August 21, 2021, 5:09pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/1 "2021-08-21T17:09:37Z")

</div>

Hello everyone,

I am trying to search for text in files and dynamically return the lines in the files based on the location of the found text. This is how I am finding the files with the string in it:

$found = Get-ChildItem -Path C:\Git\organization\organization-service-7 -Include _._ -Recurse | Select-String -Pattern locals

This is how I am getting the line number of the found text:

$filename, $linenumber, $searchword = $finds -split “:”, 3

I am having a really hard time figuring out the rest. Here is the logic and an example…

1. Search a folder and all subdirectories for

2. Every time the text **locals** is found in \ ***.log** files , search for the first occurance of **{** (open brace) starting on the line where **locals** was found.

3. Once **{** is found then find the closing **}** bracket. (Note: There could be other **{** and **}** encountered before the **}** you are looking for is found)

4. Once the proper } is found.  
a. Output to a text file  
1. File name  
2. The text searched for  
3. The line where the first { was found  
4. All line inbetween the { and }  
5. The line where the } (closing brace) was found

Example:  
Folder : **c:\bin\day\logs**  
Files found : **downtime.log** and **uptime.log**  
Files are in **c:\bin\day\logs**  
Search starts in **c:\bin**

Content of **downtime.log** :

locals  
{  
location\_down = {  
{country = $(current)  
{state = $(state)  
{city = $(city)  
}  
}  
}  
}

Content of **uptime.log** :

locals  
{  
location\_up = {  
{country = $(current)  
{state = $(state)  
{city = $(city)  
}  
}  
}  
}

**text file output** :

c:\bin\day\logs\downtime.log  
locals  
{  
location\_down = {  
{country = $(current)  
{state = $(state)  
{city = $(city)  
}  
}  
}  
}  
c:\bin\day\logs\uptime.log  
locals  
{  
location\_up = {  
{country = $(current)  
{state = $(state)  
{city = $(city)  
}  
}  
}  
}

Any help would be appreciated,

Ray

---

<div class="post-metadata">

**Author:** ![Olaf](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/olaf/32/992_2.png) [@Olaf](https://forums.powershell.org/u/Olaf)\
**Post date:** [August 21, 2021, 7:31pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/2 "2021-08-21T19:31:04Z")

</div>

Ramon,  
Welcome to the forum. 👋🏽

Maybe we can find a solution togehter. 😉 The structure of the search pattern you’re looking for seems pretty regular. Sounds for me like “_find the word “ **local** ” and the following 9 lines_” …

This could be achieved with Select-String and the parameter `-Context`. Try to run the following line of code _for one of your target files_ to see what I mean:

```auto
$Path = 'D:\sample\LogFile.log'
Select-String -Path $Path -Pattern locals -Context 0, 9 |
ForEach-Object {
    [PSCustomObject]@{
        FileName = $_.Filename
        LineNumber = $_.LineNumber
        Path = $_.Path
        Match = $_.Matches[0]
        PostContext = $_.Context.PostContext
    }
}

```

Now you have all you wanted - The file name, the path, the line number, the actual match and the following 9 lines. Is it that what you was looking for?

Of course you can provide `Select-String` a path with a wildcard in it to catch all files of a certain type in one directory. To search even subdirectories you would need to use a loop because `Select-String` does not recurse.

Regardless of all that could you please format your code or sample data as code using the _preformatted text_ button ( **\</\>** ) next time. That’l make it much easier to copy your code and sample data and to play around with it to be able to reproduce your challenge.

Thanks in advance.

---

<div class="post-metadata">

**Author:** ![vegarjb](https://avatars.discourse-cdn.com/v4/letter/v/ecb155/32.png) [@vegarjb](https://forums.powershell.org/u/vegarjb)\
**Post date:** [August 22, 2021, 3:16am UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/3 "2021-08-22T03:16:56Z")

</div>

Thanks for the help you are giving me some ideas. The problem is not getting 9 lines of code, it is determining the _n_ lines of code.

For example

Find **locals** on line 40 ( locals has 6 characters), so start to search for **{** on line forty position 7  
not found on 40 but found on 41. Continue searching for }. The next } is on line 46 but that belongs to line 45 {. This keeps going until line 50 where the closing brace is found. So lines 41 - 50 would be outputted

```auto
40 locals 
41 {
42 location_down = { 
43 {country = $(current)
44 {state = $(state)
45 {city = $(city)
46 }
47 }
48 } 
49 }   
50 }

Taking it to the extreme... if all of this was put on 1 line, 1 like would be returned becaue the opening and closing braces are on the same line

locals {location_down = { {country = $(current) {state = $(state {city = $(city) } } } } }

```

Thanks,

Ray

---

<div class="post-metadata">

**Author:** ![krzydoug](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/krzydoug/32/1008_2.png) [@krzydoug](https://forums.powershell.org/u/krzydoug)\
**Post date:** [August 22, 2021, 6:11am UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/4 "2021-08-22T06:11:24Z")

</div>

Hi Ray,

I must say this is confusing with some contradictions. First, when you post the sample files, you show the text with no indentation. However your last reply shows indentation. Your sample files also only contain the exact text your after, I’ll assume this file has other text you’re trying to pull this out of?

Next, when you say “The line where the first { was found” it really makes me think you want the line number where it was found. It appears Olaf may have thought the same thing. However, your sample output text file just shows the file name followed by the entire pattern you’re trying to match. Perhaps we can rephrase bullet point 4 to the following.

```
4. Output the file name followed by the entire matched pattern, all text/lines from the word locals to the appropriate closing curly brace.

```

Also, your original post (please format code as code with the \</\> button) had a missing closing curly brace. You did correct this in your last reply.  
Ok, so I added some other text to the sample files for this demonstration.

```auto
Push-Location $env:temp

Set-Content -Path .\uptime.log -Value @'

some random text
some random textsome random text
some random textsome random textsome random text

locals
{
location_up = {
{country = $(current)
{state = $(state)
{city = $(city)
}
}
}
}
}
some random text
some random textsome random text
some random textsome random textsome random text
'@

Set-Content -Path .\downtime.log -Value @'
some random text
some random textsome random text
some random textsome random textsome random text

locals
{
  location_down = {
    {country = $(current)
      {state = $(state)
        {city = $(city)
        }
      }
    }
  }
}

some random text
some random textsome random text
some random textsome random textsome random text
locals
{
  location_down = {
    {country = $(current)
      {state = $(state)
        {city = $(city)
        }
      }
    }
  }
}
some random text
some random textsome random text
some random textsome random textsome random text
'@

```

And now that I have a couple of sample files, here is one possible way to achieve (what I believe is) your goal.

```auto
Set-Content -Path .\output.txt -Value $(
    foreach($logfile in Get-ChildItem -Filter *.log) {
        $output = $false
        $braces = 0
    
        switch -Regex (Get-Content -Path $logfile.FullName){
            'locals' {
                $logfile.FullName
                $output = $true
                $_
            }

            '{' {
                if($output){
                    $braces++
                    $_
                }
            }

            '}' {
                if($output){
                    $braces--
                    $_
                }
            
                if($braces -eq 0){
                    $output = $false
                }
            }

            default {if($output){$_}}
        }
    }
) -PassThru

Pop-Location

```

Now if we could always depend on the final closing curly brace to be at the beginning of a line and the rest will have one or more spaces before them, we could use something as simple as this.

```auto
Push-Location $env:temp

[regex]$pattern = '(?s)locals.+?{.+?(?<! )}'

Get-ChildItem -Filter *.log | ForEach-Object {
    foreach($match in $pattern.Matches((Get-Content $_.FullName -Raw))){
        $_.FullName
        $match.Value
    }
} | Set-Content .\output.txt

Pop-Location

```

---

<div class="post-metadata">

**Author:** ![vegarjb](https://avatars.discourse-cdn.com/v4/letter/v/ecb155/32.png) [@vegarjb](https://forums.powershell.org/u/vegarjb)\
**Post date:** [August 22, 2021, 9:46pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/5 "2021-08-22T21:46:10Z")

</div>

krzydoug,

Thank you very much. Sorry for not formatting the first post. I just joined and it was my first post. I was asked to format it in my reply. Sorry for the confusion. It is always easy to understand something if it is yours but sometime difficult to verbalize. (sp?)

Let me try harder:

Step 1 - Search for all files in a folder and it subfolders that contains the word **“locals”**.

Step 2 - In every file that the word **“locals”** is found, start at the word “locals” and search for the an open brace.

Step 3 - Starting right after the opening brace , continue searching to the end of line and every line after until a closing brace is found.

Step 4 - Output the filename, textsearched for (in this case **“locals”** ) and the text found between the braces.

Example input:

```auto
locals 
{
  location_down = { 
      {country = $(current)
         {state = $(state)
             {city = $(city)
            }
        }
    }
}

```

Example output:

```auto
Filename: main.txt
Word to search for : locals

{
  location_down = { 
      {country = $(current)
         {state = $(state)
             {city = $(city)
            }
        }
    }
}

```

Note: Sometimes the word **“locals”** and the opening brace may appear on the same line.

I have not had time to go through your code yet, but I will and respond.

Thanks,

Ray

---

<div class="post-metadata">

**Author:** ![krzydoug](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/krzydoug/32/1008_2.png) [@krzydoug](https://forums.powershell.org/u/krzydoug)\
**Post date:** [August 22, 2021, 11:37pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/6 "2021-08-22T23:37:54Z")

</div>

Hi Ray,

Yeah definitely try the code out, it should be very close to what you’re after. Also, don’t want to nitpick but again you say “output the text found between the braces” and then in your example output show the braces in addition. 😉

---

<div class="post-metadata">

**Author:** ![krzydoug](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/krzydoug/32/1008_2.png) [@krzydoug](https://forums.powershell.org/u/krzydoug)\
**Post date:** [August 22, 2021, 11:47pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/7 "2021-08-22T23:47:28Z")

</div>

You may also try these.

```auto
Set-Content -Path .\output.txt -Value $(
    foreach($logfile in Get-ChildItem -Filter *.log) {
        $output = $false
        $braces = 0
    
        switch -Regex (Get-Content -Path $logfile.FullName){
            'locals' {
                "Filename: $($logfile.FullName)"
                "Word to search for : locals"
                ""
                $output = $true
            }

            '{' {
                if($output){
                    $braces++
                    $_
                }
            }

            '}' {
                if($output){
                    $braces--
                    $_
                }
            
                if($braces -eq 0){
                    $output = $false
                    ""
                }
            }

            default {if($output){$_}}
        }
    }
) -PassThru

```

Or

```auto
[regex]$pattern = '(?s)locals(.+?{.+?(?<! )})'

Get-ChildItem -Filter *.log | ForEach-Object {
    foreach($match in $pattern.Matches((Get-Content $_.FullName -Raw))){
        "Filename: $($_.FullName)"
        ""
        "Word to search for : locals"
        $match.Value -replace 'locals'
        ""
    }
} | Set-Content .\output.txt -Passthru

```

---

<div class="post-metadata">

**Author:** ![Olaf](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/olaf/32/992_2.png) [@Olaf](https://forums.powershell.org/u/Olaf)\
**Post date:** [August 23, 2021, 12:14am UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/8 "2021-08-23T00:14:50Z")

</div>

Here is some great stuff to read about the regex technique relevant for your task:

> **[Regex Tutorial - Matching Nested Constructs with Balancing Groups](https://www.regular-expressions.info/balancing.html)**
>
> In .NET’s regex flavor you can use balancing groups to match nested constructs.

---

<div class="post-metadata">

**Author:** ![dicey](https://avatars.discourse-cdn.com/v4/letter/d/ecd19e/32.png) [@dicey](https://forums.powershell.org/u/dicey)\
**Post date:** [August 23, 2021, 8:37am UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/9 "2021-08-23T08:37:02Z")

</div>

@vegarjb - this is a bit rough, but it’s gotten late after a long day of restoring a guest bathroom. If it meets your original post, it might provide a framework to continue development.

## Process

I created three source files and set each with their own `locals` array. This was to test the regex against different patterns and false positives. I recommend establishing standards for your procedure and streamlining the process where you may.

### Notes

- This is demo code. It is a Proof of Concept and does not represent stable production-quality scripting.
- The one-liner `locals` array has a `SearchStart` of 0. This needs to be handled to prevent misreporting.

## Demo Code

```powershell
using namespace System.Collections;

Class TextBody
{
    hidden [string] $FileName;
    hidden [int] $GroupID;
    hidden [string] $LineContent;
    hidden [int] $LineID;
}

Class TextHeader : TextBody
{
    [string] $Body;
    [string] $FileName;
    [int] $SearchStart;
    [int] $SearchEnd;
    [string] $SearchTerm;
}

[string]$searchTerm = 'locals';
$files = (Get-ChildItem "A:\bin\day\logs").Where({$_.Extension -eq '.log'});
$locals = [ArrayList]::new();
[int]$count = 1;

foreach($file in $files)
{
    if((Get-Content $file.FullName -Raw) -match "\b$($searchTerm)\b(?=\s*[{])")
    {
        $x = (Get-Content $file.FullName);
        if($x.IndexOf( $x.Where({$_ -match "\b$($searchTerm)\b\s*[{]"})) -eq 1)
        {
            [int]$idx = $x.IndexOf( $x.Where({$_ -match "\b$($searchTerm)\b"}))
        }
        else
        {
            [int]$idx = $x.IndexOf( $x.Where({$_ -match "\b$($searchTerm)\b\s*[{]"}))
        }
   
        for($i = $idx ; $i -le $x.IndexOf($x.Where({$_ -match "}"})[-1]); $i++)
        {       
            $y = [TextBody]::new();
            $y.FileName = $file.FullName;
            $y.GroupID = $count;
            $y.LineContent = $x[$i];
            $y.LineID = $i;
            [void]$locals.Add($y);
        }
    }
    $count++;
}

$localsOut = [ArrayList]::new();
for ($j = 1; $j -le ($locals|select -Unique GroupID).Count; $j++)
{
    $u = $locals.Where({$_.GroupID -eq $j})
    $z = [TextHeader]::new();
    $z.Body = $u.LineContent | Out-String;
    $z.FileName = $u.FileName|select -First 1;
    $z.SearchStart = $u.LineID[2];
    $z.SearchEnd = $u.LineID[-1];
    $z.SearchTerm = $searchTerm;
    [void]$localsOut.Add($z);
}
$localsOut | Select-Object FileName,SearchTerm,SearchStart,SearchEnd,Body | Out-File A:\bin\output.txt -Force;

```

## Results

### `$localsOut`

 ![image](https://us1.discourse-cdn.com/flex019/uploads/powershell/original/2X/2/2f97704787fa8e007777ba030f9f0d681d6a15c3.png)

### Output.txt

_The file output can be cleaned with better formatting. The red is lint running in my VS Code._

 ![image](https://us1.discourse-cdn.com/flex019/uploads/powershell/original/2X/3/3986fdc3c6cb6487f7b283857b45d54f573effbc.png)

### Uptime.log

 ![image](https://us1.discourse-cdn.com/flex019/uploads/powershell/original/2X/2/22980d674ca0501837206b3486bc9096a5e98541.png)

### Downtime.log

 ![image](https://us1.discourse-cdn.com/flex019/uploads/powershell/original/2X/8/8d1fd17da64c2e885a992d5ca0481dfe3efc5300.png)

### Downtime\_002.Log

 ![image](https://us1.discourse-cdn.com/flex019/uploads/powershell/original/2X/2/29f9d226213fa96ac2de3d48246323d38e8dc27b.png)

---

<div class="post-metadata">

**Author:** ![dotnVo](https://avatars.discourse-cdn.com/v4/letter/d/4af34b/32.png) [@dotnVo](https://forums.powershell.org/u/dotnVo)\
**Post date:** [May 16, 2024, 8:21pm UTC](https://forums.powershell.org/t/search-for-text-then-return-text-inbetween-braces/17371/10 "2024-05-16T20:21:43Z")

</div>


