2

I have a text file that contains:

#define VERSION "0.1.2"

I need to replace that version number from a running batch file.

set NEW_VERSION="0.2.0"

powershell -Command "(gc BBB.iss) -replace '#define VERSION ', '#define VERSION %NEW_VERSION% ' | Out-File BBB.iss"

I know that my match pattern is not correct. I need to select the entire line including the "0.2.0", but I can't figure out how to escape all that because it's all enclosed in double quotes so it runs in a batch file.

I'm guessing that [0-9].[0-9].[0-9] will match the actual old version number, but what about the quotes?

pizzafilms
  • 3,829
  • 4
  • 24
  • 39

2 Answers2

1

but what about the quotes?

When calling PowerShell's CLI from cmd.exe (a batch file) with powershell -command "....", use \" to pass embedded ".

(This may be surprising, given that PowerShell-internally you typically use `" or "" inside "...", but it is the safe choice from the outside.[1].)

Note:

  • While \" works robustly on the PowerShell side, it can situationally break cmd.exe's parsing. In that case, use "^"" (sic) with powershell.exe (Windows PowerShell), and "" with pwsh.exe (PowerShell (Core) 7+), inside overall "..." quoting. See this answer for details.

Here's an approach that matches and replaces everything between "..." after #define VERSION :

:: Define the new version *without* double quotes
set NEW_VERSION=0.2.0

powershell -Command "(gc BBB.iss) -replace '(?<=#define VERSION\s+\").+?(?=\")', '%NEW_VERSION%' | Set-Content -Encoding ascii BBB.iss"

Note that using Out-File (as used in the question) to rewrite the file creates a UTF-16LE ("Unicode") encoded file, which may be undesired; use Set-Content -Encoding ... to control the output encoding. The above command uses Set-Content -Encoding ascii as an example.
Also note that rewriting an existing file this way (read existing content into memory, write modified content back) bears the slight risk of data loss, if writing the file gets interrupted.

  • (?<=#define VERSION\s+\") is a look-behind assertion ((?<=...)) that matches literal #define VERSION followed by at least one space or tab (\s+) and a literal "

    • Note how the " is escaped as \", which - surprisingly - is how you need to escape literal " chars. when you pass a command to PowerShell from cmd.exe (a batch file).[1]
  • .+? then non-greedily (?) matches one or more (+) characters (.)...

  • ...until the closing " (escaped as \") is found via (?=\"), a look-ahead assertion
    ((?<=...))

  • The net effect is that only the characters between "..." are matched - i.e., the mere version number - which then allows replacing it with just '%NEW_VERSION%', the new version number.


A simpler alternative, if all that is needed is to replace the 1st line, without needing to preserve specific information from it:

powershell -nop -Command "@('#define VERSION \"%NEW_VERSION%\"') + (gc BBB.iss | Select -Skip 1) | Set-Content -Encoding ascii BBB.iss"

The command simply creates an array (@(...)) of output lines from the new 1st line and (+) all but the 1st line from the existing file (gc ... | Select-Object -Skip 1) and writes that back to the file.


[1] When calling from cmd.exe, escaping an embedded " as "" sometimes , but not always works (try
powershell -Command "'Nat ""King"" Cole'").
Instead, \"-escaping is the safe choice.
`", which is the typical PowerShell-internal way to escape " inside "...", never works when calling from cmd.exe.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Yes it works, partially. Despite me escaping all inner quotes, batch reports an error because it tries to run a powershell command as batch command, since it seems to parse the quotes on the batch interpreter level and they are odd not even in count. Screw you Microsoft. For record, this is the command that causes problems: `powershell -NoLogo -NoProfile -Command "(Get-Content ACTM.aip) -replace ' – Martin Braun Mar 28 '22 at 21:56
  • The working better solution is this: `powershell -NoLogo -NoProfile -Command "(Get-Content ACTM.aip) -replace '(?<= – Martin Braun Mar 28 '22 at 22:03
  • Yes, @MartinBraun, `cmd.exe`'s parsing is really a shame; I've since learned a way to avoid the `"`-related problems by escaping them as `"^""` (sic) instead of `\"`; with `pwsh.exe` (PowerShell (Core) 7+), `""` works robustly. – mklement0 Mar 28 '22 at 22:20
  • Thanks for your reply, this sounds really promising, but I fail to not use `\"` in my above comment. Could you please convert it so it would work no matter how many quotes I have in my command? I cannot update PowerShell and need to use core version 6. Is this exclusively for version 7? – Martin Braun Mar 28 '22 at 22:58
  • 1
    @MartinBraun, try the following (unfortunately, I am _not_ kidding): `powershell -NoLogo -NoProfile -Command "(Get-Content ACTM.aip) -replace '(?<= – mklement0 Mar 29 '22 at 12:13
  • 1
    @MartinBraun, btw your use of `powershell.exe` implies that you're using _Windows PowerShell_, whose latest and last version is 5.1 The CLI for PowerShell (Core) - the modern, cross-platform edition that starts with v6 and is now at v7.2.2 - is `pwsh.exe` - as I noted, with `pwsh.exe` you can use `""` – mklement0 Mar 29 '22 at 12:16
-1

You can try this,

powershell -Command "(gc BBB.iss) -replace '(?m)^\s*#define VERSION .*$', '#define VERSION %NEW_VERSION% ' | Out-File BBB.iss"

If you want double quotes left,

powershell -Command "(gc BBB.iss) -replace '(?m)^\s*#define VERSION .*$', '#define VERSION "%NEW_VERSION%"' | Out-File BBB.iss"
Thm Lee
  • 1,236
  • 1
  • 9
  • 12
  • You don't need the multiline inline option `(?m)` here, because `gc` (`Get-Content`) outputs an _array_ of lines, on which `-replace` acts _individually_, so `^` by definition only ever refers to the start of each line. (As an aside: if the input _were_ a multiline string, `\s*` would greedily match whitespace _including newlines_.) Your 2nd command won't work as intended, because the `"` chars. in the replacement string aren't escaped. – mklement0 Apr 20 '18 at 02:05
  • @mklement0, Thanks for your advice. But, have you tried it? It works and in the single quotes, it seems not need to escape double quote. – Thm Lee Apr 20 '18 at 02:18
  • Actually, it does _not_ work: the _closing_ `"` is missing - at least when I run your 2nd command (from a `cmd.exe` command prompt or from a batch file) on Windows 10 with v5.1.16299.251. And the - obscure - reason it _half_ works is because `%NEW_VERSION%`'s value has _embedded_ double quotes, so what PowerShell sees after `cmd.exe`'s expansion is `""0.2.0""`, which - due to the effective doubling of `"` - amounts to escaping of the enclosing `"`, which preserves them in the replacement, but, as stated, only the _opening_ `"`. The safe choice is to escape embedded `"` chars. as `\"`. – mklement0 Apr 20 '18 at 03:40
  • Your 2nd command - the one that matters, given that the OP is attempting to preserve the double quoting - is still broken, so it's a -1 from me. If you think my diagnosis is incorrect, do tell me. Here's a simplified version of your command to verify that it's broken / to experiment with: Run `set NEW_VERSION="0.2.0"` first, and then `echo #define VERSION "0.1.0" | powershell -nop -command "$input -replace '#define VERSION .*$', '#define VERSION "%NEW_VERSION%"'"` – mklement0 Apr 20 '18 at 13:00