.bat批處理(九):替換帶有等號=的字符串的子串

前言

今天寫這篇記錄要解決的問題來源於最近一名讀者的提問,之前寫過一篇名爲《.bat批處理(六):替換字符串中匹配的子串》的總結文章,結果有讀者在評論區提問說,如果想要替換的子串中包含等號 =,那麼就無法替換了,問有沒有什麼辦法可以解決。遇到這個問題的第一感覺應該挺好處理的吧,如果批處理程序在替換操作中認爲等號 = 比較特殊,那就加個轉義字符應該就可以了,但事實卻證明這種想法有些天真了。

在嘗試多次失敗之後,我意識到事情遠沒有想象的那麼簡單,開始在網上尋找解決方案,結果有些讓人意外,絕大多數人都說這是 SET 命令的執行規則決定的,無法實現這種需求。當要替換的子串中包含 = 時,第一個 = 就會被認爲是替換語法中的 =,進而導致無法得到正確的結果,即使是使用轉義字符都無法完成正確替換,加入的轉義字符會影響匹配,導致替換失敗。還有一些人建議用其他工具來完整這種需求,比如記事本的替換功能 O(∩_∩)O

遇到的問題

看了上面的敘述,可能有些小夥伴對我所說的問題還沒有太直觀的認識,接下來我們舉個例子來說一下這個問題究竟是怎樣產生的。

0x00 帶有 = 的字符串

首先需要被替換的字符串中要包含等號,我們來定義一個這樣的變量:

set STR=abcdo=ocar12a=ajdjko=ot

變量的名字是 STR,變量的值是 abcdo=ocar12a=ajdjko=ot,其中包含了三個 =

0x01 帶有 = 的想要被替換的子串

確定一下我們想要替換的子串 o=o,假如我們想把它替換成字母 A,按照一般的替換規則X:Y=Z,在 X 串中尋找到 Y 串之後把它替換成 Z 串,實現的代碼如下:

@echo off

set STR=abcdo=ocar12a=ajdjko=ot
set RESULT=%STR:o=o=A%

echo %RESULT%
pause > nul

運行之後的結果是:

abcdo=A=o=Acar12a=ajdjko=A=o=At

和我們想法不一樣,我們本來想把 o=o 替換成 A,但是從結果來看應該是把 o 替換成了 o=A,原因就是我們選擇的被替換中的子串 o=o 包含一個 =,而這個 = 被當成了替換語法 X:Y=Z 中的 =,所以就不對了。

0x02 嘗試用轉義字符來處理

很多語言中都有轉義字符,比如 Markdown 語法中的反斜槓 \,在 Markdown 語法中被星號 * 包裹的文字是傾斜的,但是如果想正常的輸出一個 * 怎麼辦呢?就需要在 * 前面加一個反斜槓 \,變成 \*,這樣 * 原本的傾斜文字的作用就被轉義了,變成了一個普通的輸出字符。

在批處理中也有轉義字符的概念,它就是 ^,我們知道在批處理中 >| 等符號都是有特殊用處的,所以不能簡單的輸出,比如 echo > 是無法輸出一個大於號的,要寫成 echo ^> 才能正常輸出一個 > 符號。

我們就利用這個轉義字符來告訴替換命令,被替換的子串中的 = 是一個普通字符,不能作爲替換規則的一部分,所以被替換的子串寫成了 o^=o,我們實現下面的代碼,看看能不能達到目的:

@echo off

set STR=abcdo=ocar12a=ajdjko=ot
set RESULT=%STR:o^=o=A%

echo %RESULT%
pause > nul

運行之後結果如下:

abcdo=ocar12a=ajdjko=ot

與替換前對比發現沒有任何變化,看來轉義字符的想法沒能幫助我們解決問題,還是想想其他的辦法吧。

穩紮穩打的解決方案

既然 = 這麼特殊,我們就先想辦法幹掉等號,直接替換的方式不好使,我們可以一個字符一個字符的判斷啊,雖然麻煩一點,但是解決問題纔是最重要的。

既然要一個個的字符去判斷,就需要遍歷原字符串,最簡單的可以使用字符串分割啊,語法爲 原串:~偏移,長度 就可以了,如果不太清楚可以參考一下 《.bat批處理(三):變量聲明、設置、拼接、截取》,截取第一個字符的語法是 原串:~0,1, 截取第二個字符的語法是 原串:~1,1,以此類推。

具體的思路就是我們先判斷第一個字符,如果是 = 就進行替換,如果不是 = 就放到結果字符串裏,然後繼續判斷第二個字符進行操作,最後所有的字符處理一遍就完成了替換。

需要使用 goto 語句來寫一個循環,代碼邏輯比較簡單,就是遍歷所有字符,是 = 就替換,不是 = 就保留,假設我們先把 = 替換成 #,實現的代碼如下:

@echo off

set STR=abcdo=ocar12a=ajdjko=ot
set CURSTR=%STR%
set RESULT=

:next
if "%CURSTR%" equ "" goto end
set a=%CURSTR:~0,1%

if "%a%" equ "=" (set RESULT=%RESULT%#) else (set RESULT=%RESULT%%a%)
set CURSTR=%CURSTR:~1%
goto next

:end
echo source string is %STR%
echo result string is %RESULT%
pause > nul

:next 是循環的入口,每次截取第一個字符,判斷是 = 就在結果中拼接 # 字符,相當於完成了替換,如果字符不是 = ,就將字符直接拼接到結果中,操作之後將原串的第一個字符刪除形成新的原串,然後再判斷第一個字符,以此類推,直到原串爲空,運行結果如下:

source string is abcdo=ocar12a=ajdjko=ot
result string is abcdo#ocar12a#ajdjko#ot

最終方案

事情到了這裏好像還沒完,在實際操作中有些情況不是替換一個 =,往往是替換的內容中包含 =,上面將 = 替換成 # 不具有通用型,如果是一開始的請求,將 o=o替換成 A 就不能這樣寫了,就應該是每次判斷3個字符了,寫起來有些麻煩,批處理中沒有獲得字符串長度的函數,需要自己實現一個,如果是100個字符的被替換串,那代碼就很難寫了。

既然 = 都能被我們替換掉,肯定有辦法實現上面我們這種將 o=o替換成 A 的要求,下面我們就列舉一種通用的處理方法。

0x00 首先將 = 替換成一個原串中不可能出現的字符或者序列

這步替換可能最後需要還原的,所以要求我們替換成的目標序列不能在原串中出現,比如我們上面把 = 替換成了 #, 如果原串中有 # 就會弄混了,不能確定是原來字符串中就存在的 #,還是由 = 變成的 #

這個序列我們可以定義的變態一點,比如把 = 替換成 ###i#am#happy###,我們把它記作 α

0x01 用這個不能出現序列替換我們之前要查找替換子串中的 =

我們之前要查找替換的子串是 o=o,那麼替換之後形成 o###i#am#happy###o,我們把它記作 β

0x02 將第1步結束獲得的替換結果作爲原串,將其中的 β 替換成 A

其實就是把第1步替換完結果作爲原串,把其中的 o###i#am#happy###o 也就是原來的 o=o 替換成 A

0x03 將第3步結果的子串作爲原串,將其中的 α 替換爲 =

這一步就是處理那些雖然是 =,但是這個 = 不是我要替換的結果子串中的,所以要還原

代碼實現

步驟梳理清楚了,下面來寫代碼,按照步驟一步步寫就可以了:

@echo off

rem 第一步
set CORESTR=###i#am#happy###
set STR=abcdo=ocar12a=ajdjko=ot
set CURSTR=%STR%
set RESULT1=

:next1
if "%CURSTR%" equ "" goto end1
set a=%CURSTR:~0,1%

if "%a%" equ "=" (set RESULT1=%RESULT1%%CORESTR%) else (set RESULT1=%RESULT1%%a%)
set CURSTR=%CURSTR:~1%
goto next1

:end1
echo source1 string is %STR%
echo result1 string is %RESULT1%
pause > nul


rem 第 2 步
set CORESTR=###i#am#happy###
set STR=o=o
set CURSTR=%STR%
set RESULT2=

:next2
if "%CURSTR%" equ "" goto end2
set a=%CURSTR:~0,1%

if "%a%" equ "=" (set RESULT2=%RESULT2%%CORESTR%) else (set RESULT2=%RESULT2%%a%)
set CURSTR=%CURSTR:~1%
goto next2

:end2
echo source2 string is %STR%
echo result2 string is %RESULT2%
pause > nul


rem 第3步,需要開啓延遲變量
setlocal ENABLEDELAYEDEXPANSION
set RESULT3=!RESULT1:%RESULT2%=A!
echo result3 string is %RESULT3%
pause > nul


rem 第4步
set RESULT4=!RESULT3:%CORESTR%==!

echo finally result is %RESULT4%

運行之後的結果爲:

source1 string is abcdo=ocar12a=ajdjko=ot
result1 string is abcdo###i#am#happy###ocar12a###i#am#happy###ajdjko###i#am#happy###ot
source2 string is o=o
result2 string is o###i#am#happy###o
result3 string is abcdAcar12a###i#am#happy###ajdjkAt
finally result is abcdAcar12a=ajdjkAt

這次終於替換成功了,o=o 被成功替換成了字母 A,代碼中用到了延遲變量,主要是爲了實現被替換字符串是變量的情況,不清楚延遲變量的用法可以簡單查詢一下,至此文章開頭提出的問題我們就成功解決了,雖然路途有些坎坷。

總結

  1. 批處理程序中的 = 比較特殊,使用常規的 X:Y=Z 的語法不能替換包含 = 的子串
  2. 遇到上述情況可以將字符串切割,採用逐個字符比較的方式,將 = 替換成其他字符再進行後續操作
  3. 有時候也不必非得使用批處理來替換包含 = 的字符串,隨便一個文本工具,比如記事本都可以文本進行替換
  4. 如果非得用命令解決,也可以使用從 linux 的 sed 命令移植到 windows 的 sed.exe 程序來很方便的進行替換
  5. 使用 sed 命令的語法是 echo abcdo=ocar12a=ajdjko=ot | sed -e "s/o=o/A/g",一步就可以完成了文章開頭的需求了
  6. 如果你暫時沒有 sed.exe 程序,可以點擊這個鏈接 sed.exe程序 下載,若不是在同一目錄使用,記得將命令目錄添加到環境變量中

時間慢慢地磨去了年少輕狂,也漸漸地沉澱了冷暖自知。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章