2

I need to replace

location ~* "^/(888888-localhost\/client)(.*)$" {
            proxy_pass https://localhost:32583;

by

location ~* "^/(888888-localhost\/client)(.*)$" {
            proxy_pass https://localhost:$proxy;

I'm trying to achieve this with the following code with no success

Get-ChildItem 'C:\nginx\conf\nginx.conf' | ForEach {
     (Get-Content $_) | ForEach  {$_ -Replace "location ~\* \""^/\(888888-localhost\\/client\)(.*)$"" {
            proxy_pass https://localhost:.+;", "location ~* ""^/(888888-localhost\/client)(.*)$" {
                proxy_pass https://localhost:$proxy;""} | Set-Content $_
}

How can i do this using Powershell?

zett42
  • 25,437
  • 3
  • 35
  • 72
wolfdebs
  • 153
  • 7
  • Is `$proxy` a variable holding a new value you want interpolated in the string or you want the literal string? – Santiago Squarzon Aug 19 '22 at 16:44
  • I want to replace the variable – wolfdebs Aug 19 '22 at 16:45
  • 1
    So you **only** want to replace the port with whatever port number you put in `$port`? – Abraham Zinala Aug 19 '22 at 16:50
  • Yes, i need to replace only the port, but for multiple blocks. In this example is for the Client app, but i have other apps which i need to replace as well, so i can't replace only the `localhost:$port` for the whole file, otherwise all apps would get the same port number. – wolfdebs Aug 22 '22 at 09:55

2 Answers2

1
  • Since you want to match across lines, you cannot use the line-by-line processing that Get-Content does by default. Use the -Raw switch to read the entire file at once, into a single, multi-line string.

  • Since both your search string and the replacement string passed to the regex-based -replace operator contain metacharacters, you must escape them.

    • Note: If all you need is literal (verbatim) substring matching, including not needing to assert where the substring must match (start of the string, word boundary, ...), using [string]::Replace() is the simpler option, however:

      • In Windows PowerShell [string]::Replace() is invariably, case-sensitive, in PowerShell (Core) 7+ it is by default, unlike PowerShell's operators - see this answer for guidance on when to use -replace vs. [string]::Replace()
    • The search string must be escaped with [regex]::Escape(), so that the .NET regex engine, which the -replace operator invariably uses, treats it as a literal.

    • The substitution string must have any embedded $ characters escaped as $$, so that $ isn't mistaken for the start of a capture-group reference.

    • For a comprehensive overview of how -replace works, see this answer.

# Escape the search string.
$reSearch = [regex]::Escape(@'
location ~* "^/(888888-localhost\/client)(.*)$" {
            proxy_pass https://localhost:32583;
@')

# Escape the substitution string.
$substitution = @"
location ~* "^/(888888-localhost\/client)(.*)$" {
            proxy_pass https://localhost:$proxy;
"@.Replace('$', '$$')

# ...
  ($_ | Get-Content -Raw) -replace $reSearch, $substitution |
    Set-Content -LiteralPath $_.FullName
# ...

Note the use of both verbatim and expandable here-strings to facilitate declaring the strings.

As for a potential simplification of your -replace operation:

  • Abraham Zinala points out that ultimately you're looking to only replace the port number in the matched substring, so you could use a positive look-behind assertion ((?<=...)), as shown in this simplified example:

    $proxy = 8080
    'a https://localhost:80; z' -replace '(?<=https://localhost:)\d+', $proxy
    
    • Output is: a https://localhost:8080; z

Caveat re newlines:

  • The above assumes that your script file uses the same newline format (Windows-format CRLF vs. Unix-format LF) as the input file.

  • If you're not sure, and you want to match either format, replace the \n escape sequence in $reSearch with \r?\n ($reSearch = $reSearch.Replace('\n', '\r?\n') and possibly replace the literal newline in $substitution with `r`n or `n, as needed.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

One solution would be to use a positive lookbehind to ensure the pattern is matched:

@"
location ~* "^/(888888-localhost\/client)(.*)$" {
            proxy_pass https://localhost:32583;
"@  -replace "(?<=\/localhost:)\d+",'$port'

# In your use-case
(Get-Content -Path ($Path = 'C:\nginx\conf\nginx.conf')) -replace "(?<=\/localhost:)\d+",'$port' | 
    Set-Content -Path $Path

Removing the single quotes around $port, or replacing them with double quotes will allow the use of $port as an actual variable.

Abraham Zinala
  • 4,267
  • 3
  • 9
  • 24
  • 1
    Oops! I saw that your answer was temporarily deleted, so I added the salient points to mine (giving you credit). As for a quick question: sure, fire away. As you said, we can always clean up the comments. – mklement0 Aug 19 '22 at 17:31
  • 1
    So the look-behind assertion is definitely a good idea, but note that it may be necessary to match _both_ the lines in the original search string to avoid false positives. – mklement0 Aug 19 '22 at 17:33
  • 1
    @mklement0, no worries, thought I misunderstood the question so I deleted it, then undid it. The question was in regards to single quotes. I may be thinking too much into it but, I will probably post it on Twitter instead lol – Abraham Zinala Aug 19 '22 at 17:37
  • 1
    A quick thought on single quotes: I recommend using them routinely, both for regexes and substitution expressions, and to use double quotes only if interpolation is needed, though even then something like `'a{0}z' -f $var` may be preferable. – mklement0 Aug 19 '22 at 17:41
  • 1
    Relevant answer to my previous comment: https://stackoverflow.com/a/50216562/45375 – mklement0 Aug 19 '22 at 17:43
  • 1
    I'd say robustness (avoiding accidental expansion) and conceptual clarity are the primary reasons to use only `'...'` for verbatim text. You're correct that less work should be required for verbatim strings, but in practice both forms perform the same, curiously. Note that, once created (possibly involving interpolation), .NET strings are also immutable. – mklement0 Aug 19 '22 at 17:59
  • This would work if it was only one occurrence to replace, but unfortunately i'm using `client` to search the block i'm going to replace. I need to replace ports for client app, auth app, token app... – wolfdebs Aug 22 '22 at 09:54