PowerShell命令與指令碼(12)——文字和正則表示式
阿新 • • 發佈:2021-06-26
PowerShell定義文字
使用引號可以定義字串,如果想讓自己定義的字串原樣輸出,可以使用單引號。 如果想讓自己的定義的字元中的變數被內容替換,表示式被執行可以使用雙引號. 1、文字中的特殊字元 如果文字放置在一個閉合的雙引號中,Powershell直譯器會去尋找特殊字元.在這方便主要有兩種特殊字元,一個是變數的字首“$”,一個是反引號“`”位於數字鍵1左邊。 處理變數 將變數放在字串中,輸出時變數會被替換成變數本身的值或者內容。如果將表示式放置在字串中,並且使用的格式如“$(expression)”,表示式也會被執行,並被替換成表示式執行的輸出。 Powershell轉義字元 在其它程式語言中喜歡將反斜槓作為轉義字元,但是在Powershell中扮演轉義字元角色的不是反斜槓,而是反引號“`”字串中的反引號,會對緊跟隨其後的字元進行特殊處理。例如下面的,在一個字串中輸出雙引號,和換行符。 其它的轉義字元如下表使用特殊文字命令
1. 字串操作符
1、格式化字串 格式化操作符 –F 能夠將一個字串格式化為指定格式,左邊是包含萬用字元的字串,右邊是待插入和替換的字串。 -F 右邊的表示式必選放在圓括號中,作為一個整體,先進行計算,然後在格式化。否則可能會解析錯誤: 可以在-F的左邊放置多個字串萬用字元,類似.NET中的String.Format方法。-F右邊相應的值或表示式也須要使用逗號分隔。 所有的基本操作符形式都大同小異,要處理的資料位於操作符的左右兩邊,然後通過操作符建立連線。例如,你可以使用下面的語句將文字中指定的字串替換成目標文字: -replace操作符有三種實現方式,其它文字操作符也類似地有三種實現方式,像-replace- Index:
- Alignment:
- Format:
[appdomain]::currentdomain.getassemblies() | ForEach-Object {
$_.GetExportedTypes() | Where-Object {! $_.IsSubclassof([System.Enum])}
} | ForEach-Object {
$Methods = $_.getmethods() | Where-Object {$_.name -eq "tostring"} |%{"$_"};
If ($methods -eq "System.String ToString(System.String)") {
$_.fullname
}
}
例如,其中的資料型別 ”全域性唯一標示符”:
System.Guid
因為你會經常使用到它,它是全球通用的,下面會給你一個簡單的例子來建立GUID。
擴充套件:GUID(全域性唯一識別符號) 全域性唯一識別符號(GUID,Globally Unique Identifier)是一種由演算法生成的二進位制長度為128位的數字識別符號。GUID主要用於在擁有多個節點、多臺計算機的網路或系統中。在理想情況下,任何計算機和計算機叢集都不會生成兩個相同的GUID。GUID 的總數達到了2^128(3.4×10^38)個,所以隨機生成兩個相同GUID的可能性非常小,但並不為0。所以,用於生成GUID的演算法通常都加入了非隨機的引數(如時間),以保證這種重複的情況不會發生。3、固定寬度的製表輸出 在一個固定寬度和對齊格式中,顯示輸出多行文字,要求每一列的輸出必選具有固定的寬度。格式化操作符可以設定固定寬度輸出。 下面的例子通過DIR返回個目錄的中的檔案列表,然後通過迴圈輸出,檔名和檔案大小,因為檔案的名字和大小都是不確定的,長度不一樣,所以結果擁擠粗糙,可讀性差。 下面固定列寬的結果,就顯得可讀性強了。要設定列寬可以將一個逗號放置在萬用字元與列寬編號的中間,負數設定右對齊,正數設定左對齊。
2. PowerShell String物件方法
從之前的章節中,我們知道PowerShell將一切儲存在物件中,那這些物件中包含了一系列中的稱之為方法的指令。預設文字儲存在String物件中,它包含了許多非常有用的處理文字的命令。例如,要確定一個檔案的副檔名,可以使用LastIndexOf()獲取最後一個字元“.”的位置,繼續使用Substring()獲取副檔名子串。 另外一條途徑,使用Split方法,對檔案的完整名稱進行分割,得到一個字串陣列,取最後一個元素,PowerShell中可以通過索引-1來獲取陣列中最後一個元素。 下面的表格會給出String物件的所有方法:3. PowerShell String類方法
使用String類命令: 之前已經討論過,物件方法和類方法的區別了,再回顧一次。 String物件衍生自string類在控制檯輸入[String]::然後按Tab鍵會自動智慧提示,這些方法就是String類命令。 Get-Member會返回所有string物件的方法,可以通過引數只返回靜態方法,也就是string類命令。使用機率最高的自然Format方法,但是因為PowerShell中已經有了大書特書的-F操作符了,Format方法可以秒殺了。但是Join和Contac還是可以聊聊的。 Join()方法曾經在上一部分演示Split()提到過,它可以將一個數組或者列表字串合以指定分隔符併成一個字串。例如自定義一個函式,移除多餘的白空格。function RemoveSpace([string]$text){
$Private:array=$text.Split(" ",[System.StringSplitOptions]::RemoveEmptyEntries)
[string]::Join(" ",$array)
}
Concat()將多個字串拼接成一個字串。
Concat()工作起來類似字串操作符“+”,類似而已,總有區別。
區別在於第一個左表示式必選是一個String型別,否則,麻煩來了:
此時可以使用:
或者:
簡單模式匹配
在驗證使用者的條目時,模式識別是必要並且常見的任務。例如判斷使用者的輸入的字串是否是一個合法的網路IP地址,或者電子郵箱。有用並且高效的模式匹配需要一些能代表確切數字和字元的萬用字元。 許多年前,人們就發明了簡單的模式匹配,一直沿用至今。#列出當前目錄中的文字檔案
dir *.txt
# 列出系統目錄中以‘n’或‘w’打頭的檔案
dir $env:windir\[nw]*.*
# 列出檔案字尾名以‘t’打頭,並且字尾名只有三個字元的檔案
dir *.t??
# 列出檔案中包含一個’e’到’z’之間任意字元的檔案
dir *[e-z].*
$ip = Read-Host "IP address"
If ($ip -like "*.*.*.*") { "valid" } Else { "invalid" }
也可以簡單驗證電子郵件地址。
$email = ".@."
$email -like "*.*@*.*"
然而上面的例子也僅能驗證一些低階錯誤,還不是很確切。例如a.b.c.d不是一個有效的IP地址,但是上面的模式匹配卻能通過驗證。
正則表示式
1. 定義模式
如果你需要更加精確的模式識別需要使用正則表示式。正則表示式提供了更加豐富的萬用字元。正因為如此,它可以更加詳細的描述模式,正則表示式也因此稍顯複雜。 使用下面的表格中列出的正則表示式元素,你可以非常精準的描述模式。這些正則表示式元素可以歸為三大類。 字元:字元可以代表一個單獨的字元,或者一個字元集合構成的字串。 限定符:允許你在模式中決定字元或者字串出現的頻率。 定位符:允許你決定模式是否是一個獨立的單詞,或者出現的位置必須在句子的開頭還是結尾。 正則表示式代表的模式一般由四種不同型別的字元構成。 文字字元:像”abc”確切地匹配”abc“字串 轉義字元:一些特殊的字元例如反斜槓,中括號,小括號在正則表示式中居於特殊的意義,所以如果要專門識別這些特殊字元需要轉義字元反斜槓。就像”\[abc\]”可以識別”[abc]”。 預定義字元:這類字元類似佔位符可以識別某一類字元。例如”\d”可以識別0-9的數字。 自定義萬用字元:包含在中括號中的萬用字元。例如”[a-d]”識別a,b,c,d之間的任意字元,如果要排除這些字元,可以使用”[^a-d]”。2. 同時搜尋不同的詞語
有時搜尋的詞語比較含糊不清,因為這些詞語可能有多種寫法。你可以使用限定符“?”來標記這些詞語作為可選字元。非常簡單,把“?”放在可選字元後面即可。這樣“?”前的字元就變成了可選字元,而不是非得出現。 注意,此處的字元“?”並不代表任何字元,因為怕你可能會聯想到簡單模式匹配裡面的“?”。正則表示式中的“?”,只是一個限定符,它代表的是指定字元或者子表示式出現的頻率。具體到上面的例子,“u?”就確保了字元“u”在模式中不是必需的。常用的其它限定符,還有“*”(出現0次後者多次)和“+”(至少出現一次)。 如果你想標記更多的連續字元作為可選,可以把這些字元放置圓括號中建立子表示式。下面的子表達可以同時識別“Nov”和“November”: 如果你想使用多個可選的搜尋詞語,可以使用“或”操作符“|”: 如果你想將搜尋的詞語和固定文字結合在一起,作為可選,仍然可以使用子表示式:3. 大小寫敏感
為了和PowerShell的習慣保持一致,操作符-match是大小寫不敏感的,如果你想切換至大小寫敏感的操作符可以使用“-cmatch” 如果你只想在模式的部分片段中使用大小寫敏感,仍舊可以使用-match,但是可以在正則表示式中指定部分模式是不是大小寫敏感。跟在“(?i)”結構後的字元大小寫不敏感,跟在(?-i)結構後面的字元大小寫敏感。下面的例子會演示這個區別,模式中的字元字串 “st”因為被(?-i)標記為大小寫敏感,所以可以匹配“TEst”,但不能匹配“TEST”。 但是如果你使用的是.NET framework的物件RegEx,它可以自動的在大小寫敏感與不敏感之間切換。既可以使用IgnoreCase引數來顯示的指定,也可以在模式中使用上面提到的結構來指定。 [regex]::matches("test", "TEST", "IgnoreCase")4. 文字中搜索資訊
正則表示式可以識別模式。它們也可以根據確定的模式從文字中過濾出資料,因此正則表示式是用來處理源文字的一款非常優秀的工具。 例如,你想從一封郵件中過濾出一個確切的電子郵件地址,就可以使用我們之前提到過正則表示式。然後就可以在變數$matches找出返回的結果。在你使用-match操作符時,$matches變數會自動被建立,並存儲過濾出的結果。$matches是一個雜湊表,你既可以輸出一個完整的雜湊表,也可以使用在中括號中的名稱(鍵值)逐個訪問其中的某個元素。 如果文字中有多個電子郵件,上面的方法還會有效嗎?非常遺憾,它不會這樣做。操作符-match只會匹配一次正則表示式。因此如果你想在源文字中搜索多個出現的模式,你必須切換至RegEx物件,值得一提的是RegEx物件不像-match,Regex物件預設是大小寫敏感的,你要想大小寫不敏感,可以參考前面的文章。5. 搜尋不同的關鍵字
你可以使用間隔結構“|”來搜尋某一組關鍵字,然後找出那些實際上出現的關鍵字: $matches會告訴你那些關鍵字實際上在字串上出現了。但是要注意正則表示式中關鍵字的順序。是因為第一個匹配到的關鍵字會被選中決定的。所以接下來的例項,結果感覺不正確: 所以接下來更改關鍵字的順序可以讓較長的關鍵字被選中。 或者,你可以更加精確地定製你的正則表示式,記住你實際上搜索的是一個獨立的單詞。所以在關鍵字中加入單詞邊界,讓順序的影響失效。 也確實這樣,-match只會搜尋第一次匹配,如果你的源文字中可能須要和包含關鍵字的多次出現,可以重新使用RegEx 上面的例子使用了.NET中的原始正則表示式類。6. 組
一串原始的文字行通常有大量有用資訊,你可以使用子表示式來收集資料,可以在之後單獨使用。基本的規則是所有想通過模式來搜尋的資料應當放在圓括號中,因為變數$matches會將這些子表示式以單獨的序列返回。如果文字行首先包含了資料,然後是其它文字,兩者之間以製表符分割,你可以如下描述這段模式: 當使用子表示式時,$matches會包含所有搜尋模式,陣列的第一個元素命名為“0”,子表示式分別位於兩個圓括號中,為了使他們更加便於讀取理解,你可以分配給每個子表示式它們自己的名子(鍵),接下來通過它們去呼叫匹配的結果。給子表示式命名,可以在圓括號中輸入type ?。 每個子表示式檢索的結果都需要儲存空間,如果特定場合中不需要這些結果可以,可以丟棄它們,因為這樣可以提高正則表示式匹配的速度。要丟棄結果,可以在子表示式中的第一個語句上加上“?:”7. 深入使用子表示式
借住子表示式的幫助,你可以創建出更加驚人和靈活的正則表示式。例如,怎樣定義一個網站中HTML標籤的模式呢?一個標籤通常包含同樣的結構:<tagname [parameter]>…</tagname>,這就意味著可以快速定義出一個非常嚴格的HTML標籤模式: 模式以固定的文字”<body “開始,額外的字元以單詞為界。接下來跟著右括號”>”,”>”之後則是中的內容,這些內容可以由任意數量的字元(.*?)組成。圓括號中是一個子表示式,會在$matches中返回檢索到的中的結果。結尾的部分為固定文字 “”)開始,另外一次以(“”)終結。如果一個正則表示式支援處理任意標籤,那它必須能夠自動地找出所有的標籤,並且在前後兩個位置都能使用。怎樣完成它呢?像這樣: 上面的正則表示式不在包含預定義的固定HTML 標籤,卻能匹配所有的HTML標籤。它是如何辦到的呢?因為初始標籤被定義成子表示式,該子表示式以字母開始,可以由任意字母或數字組成。 ([A-Z][A-Z0-9]*) 在開始匹配到的標籤必須在之後也能迭代匹配到,就是要有頭也得有尾,善始善終。此處你會發現引入了一個新寫法””,“\1”引用的是第一個子表示式。這樣就保證了HTML標籤開始的和結尾的一致了。8. 貪婪與非貪婪匹配
根據正則表示式的規則,讀者可能會懷疑在匹配HTML標籤時,使用的事“.*?”而不是簡單的“.*”。畢竟“.*”已經可以匹配足夠的字元了。“.*”和“.*?”之間的不同並不容易識別。下面通過一個例子來澄清。 假設你要再一個長檔案中匹配英文月份,但是月份並不是以同樣的方式出現的。有時使用短格式,有時使用長格式。正如接下來看見的一樣,正則表示式完成可以做到。因為正則表示式支援子表示式以可選的形式出現。 上面兩種情況正則表示式都能識別月份,但是返回的結果卻不相同,一個是Feb,一個是February。預設,正則表示式屬於“貪婪”模式。在搜尋到Feb後會繼續貪婪地搜尋更多符合模式的的字元。如果可以整個文字會返回。 然後,如果你主要關心的只是規範的月份名稱,你可能更喜歡獲取縮寫的月份名稱。這也正是“??”限定符做的,它會將正則表示式轉換成“非貪婪”模式,一旦他識別到一個模式,就會立即返回,不再會檢查可選的子表示式是否匹配。 到底限定符“??”和之前的例子中的限定符“*?”有什麼聯絡呢?事實上“*?”不是一個獨立量詞。它會將“貪婪”模式轉換成“非貪婪”模式。這就意味著,你可以使用“?”強制將限定符“*”轉換成非貪婪模式,儘可能返回短結果。9. 搜尋字元片段
舉一個例子演示怎樣通過正則表示式輕鬆的搜尋字串片段。下面會匹配位於兩個特定單詞中的字串,並且字元的長度介於1到6之間。10. 替換字串
之前介紹過-replace操作符,你可以能已經知道了怎樣替換字串中的字串。讓我們來回顧一下: 但是這種簡單的替換不可能永遠都是高效的,因此可以嘗試使用正則表示式來完成替換工作。下面有一個好玩的例子,用來演示它怎樣實用。 也許你會碰到將多個類似的詞語替換成同一個詞語這樣的需求。如果沒有正則表示式,需要重複使用replace操作符多次。而每一次replace都會伴隨一次遍歷,效率明顯很低。取而代之,如果使用正則表示式,則非常方便。 你可以在括號中輸入任意的詞語,多個詞語之間用“|”隔開,這樣所有的詞語都會被指定的字串替換掉。11. 使用反向引用
最後一個例子在一個字串中替換了多個指定的關鍵字。通常效率還是挺高的,但是有時候你可能不想替換所有出現的關鍵字,而只是想替換出現在特殊上下文中的關鍵字。這樣的情況下,上下文必須定義在模式中。例如,怎樣更改正則表示式,讓它只替換名字Miller和Meyer. 輸出結果看起來有點奇怪,但是確實是和搜尋模式匹配的。被替換掉的僅僅是Mr.或者Mrs. Miller和Mr. 或者 Mrs. Meyer。詞語”Mr. Werner”沒有被替換。遺憾的是結果沒道理替換掉整個模式,至少人名應當保留。這可能嗎? 此時反向引用應當登場了。在正則表示式中,不論你什麼時候使用圓括號,圓括號中的結果都是分開被評估的。你可以在你的“替換串”中使用這些分離出來的結果。第一個子表示式的結果總是”Mr.” 或者 “Mrs.”。第二個子表示式總是返回人名。詞語”$1” 和 “$2″在“替換串”中提供了你的子表示式(因此,數字是一串連續的數字;對於補充的子表示式你可以使用”$3″)。 奇怪的是,第一個反向引用似乎並沒有工作。當然原因也非常明顯: “$1” and “$2″看起來是PowerShell 變數, 但是實際上它們應當是操作符-replace的正則表示式詞語。導致此結果的是你把“替換串”放在了雙引號中了,PowerShell會將變數替換成具體的值,而這個值一般情況下應當為空。所以要是反向引用在“替換串”中起作用,你必須將“替換串”放置在單引號中,這樣讓$變成普通字元,這樣PowerShell就不會把它識別為自己的變量了,並完成替換功能:12. 在文字中插入字元
“替換串”可以由多行文字中的多個例項組成。例如,在你平時回覆一封郵件時,你可能在新郵件中會通過在行首新增 “>” 符號來引用原郵件的中的內容。正則表示式就可以做這樣的標記。 然而,要完成它,你可能得稍微瞭解一點“多行”模式。通常,該模式是關閉的,此時限定符”^”代表文字的開始,”$”代表文字的結束。要讓這兩個限定符可以代表文字行的開始和文字行的結束,必須使用”(?m)”來開啟“多行”模式。只有這樣,–replace 才會在每個單獨的文字行之間替換模式。在“多行”模式開啟後,限定符”^” 和 “\A”,還有”$” and “\Z”會頓時擁有不同的表現。”\A”仍然會標誌文字的開始,而”^”則會標誌文字行的開始。”\Z”仍然會標誌文字的結尾,而”$”則會標誌文字行結尾。13. 刪除多餘的空格
使用正則表示式可以完成一些日常任務,比如一處一個字串中多餘的白空格。模式需要描述一個空格(字元:“\s”)至少出現兩次(限定符:“{2,}”)。然後以一個正常的單空格字元替換。 "太多 太多的 空格 怎麼才能減少 " -replace "\s{2,}" ," "14. 搜尋和移除重複的單詞
怎樣才能移除文字中多餘的單詞。這裡,仍舊可以再次使用空格。模式可以這樣定義: "\b(\w+)(\s+\1){1,}\b" 模式會搜尋一個單詞(以“\b”定位),它由一個單片語成(字元“\w” 和限定符“+”),白空格緊隨以後(字元“\s”和限定符“?”)。該模式中,白空格字元和將要被替換的單詞必須至少出現一次(至少一次或者更多次,使用限定符“{1,}”)。整個模式會被第一次出現的反向引用給替換掉,也就是位於第一個的單詞。15. 非捕獲組
(expression) 是一種簡單的子表示式 (?:expression)是一種特殊的子表示式 後者不會將子表示式的匹配結果加入組中文字處理例項
(一) 問題描述: 有如下一段文字檔案,開頭有許多描述,字元“~”為有用資料的開始標誌,要求:求所有電阻值的個數,平均值,總和,最大值,最小值。#載入檔案,並過濾空行
$fullText=Get-Content .\a.txt | where { !([string]::IsNullOrWhiteSpace($_))}
#尋找檔案頭開始標誌
$startFlagIndex=-1
For ($i = 0; $i -lt $fullText.Length; $i++)
{
if($fullText[$i].Contains("~"))
{
$startFlagIndex=$i
break
}
}
#去掉檔案頭
$fullText=$fullText | Select-Object -Skip ($startFlagIndex+1)
<#
#將檔案轉換成CSV格式,然後再從CSV轉換成物件
#幾經周折後,再要深入進行資料處理,將會變得非常方便
#>
$objs=$fullText | foreach{
$tokens= $_.Split(' ',[StringSplitOptions]::RemoveEmptyEntries)
'"{0}"' -f [string]::Join('","',$tokens)
} | ConvertFrom-Csv
#統計放射性值不為空的物件
Write-Host "統計放射性值為空的物件"
$objs | where { $_.放射性值 -ne $null } | Format-Table -AutoSize
#求所有電阻值的個數,平均值,總和,最大值,最小值
Write-Host "求所有電阻值的個數,平均值,總和,最大值,最小值:"
$objs | Measure-Object -Property 電阻值 -Average -Sum -Maximum -Minimum
輸出示例:
回過頭再看,指令碼完全可以優化為一個foreach迴圈,每行文字只遍歷一次。之所以多次一舉,是為了演示分析問題的過程。
同樣也能得出一個結論,如果可以盡最大可能從資料來源拿到CSV檔案格式的資料,PowerShell處理起來更方便,一行搞定!
(二)
問題描述:
給出一段學生成績文字檔案如下:
$scoreTables=@{}
$stus=Get-Content .\ScoresFile.txt |
foreach {
$stu=$_ -split " "
if($scoreTables.ContainsKey($stu[1]))
{
$scoreTables[$stu[1]]++
}
else {
$scoreTables[$stu[1]]=1
}
@{ Score=$stu[1];Name=$stu[0] }
}
$stus | where {
$scoreTables[$_.Score] -gt 1
} | foreach {"{0} {1}" -f $_.Name,$_.Score }
用group-object實現統計,比較完美,稍作整理,也貼在這裡:
Get-Content .\ScoresFile.txt | ForEach-Object {
[PSCustomObject]@{
Name = $_.split()[0]
Value = $_.split()[1]
}
} | Group-Object Value | Where-Object { $_.Count -gt 1 }|
ForEach-Object { $_.Group | ForEach-Object { "{0} {1}" -f $_.name,$_.value } }
#為了和原始檔格式保持一直,加入格式化
(三)
原始文字:”data1″:111,”data2″:22,”data3″:3,”data4″:4444444,”data5″:589
要求:轉換成物件
PowerShell命令與指令碼(8)——迴圈
輸出: (四)提取CSV檔案中的域名擴充套件:CSV 逗號分隔值(Comma-Separated Values,CSV,有時也稱為字元分隔值,因為分隔字元也可以不是逗號),其檔案以純文字形式儲存表格資料(數字和文字)。純文字意味著該檔案是一個字元序列,不含必須像二進位制數字那樣被解讀的資料。CSV檔案由任意數目的記錄組成,記錄間以某種換行符分隔;每條記錄由欄位組成,欄位間的分隔符是其它字元或字串,最常見的是逗號或製表符。通常,所有記錄都有完全相同的欄位序列。 “CSV”並不是一種單一的、定義明確的格式(儘管RFC 4180有一個被通常使用的定義)。因此在實踐中,術語“CSV”泛指具有以下特徵的任何檔案:有一個CSV檔案,其中包含了成千上萬的URL連結,每個連結都可能是完整路徑包含了資料夾,變數等。希望提取出其中的域名以便於進行深度分析。 我的CSV檔案只有一列:在這些常規的約束條件下,存在著許多CSV變體,故CSV檔案並不完全互通。然而,這些變異非常小,並且有許多應用程式允許使用者預覽檔案(這是可行的,因為它是純文字),然後指定分隔符、轉義規則等。如果一個特定CSV檔案的變異過大,超出了特定接收程式的支援範圍,那麼可行的做法往往是人工檢查並編輯檔案,或通過簡單的程式來修復問題。因此在實踐中,CSV檔案還是非常方便的。
- 純文字,使用某個字符集,比如ASCII、Unicode、EBCDIC或GB2312(簡體中文環境)等;
- 由記錄組成(典型的是每行一條記錄);
- 每條記錄被分隔符分隔為欄位(典型分隔符有逗號、分號或製表符;有時分隔符可以包括可選的空格);
- 每條記錄都有同樣的欄位序列。