類似grep
的工具,它理解原始碼語法並允許除搜尋之外的操作。
與grep
一樣,正規表示式是核心原語。與grep
不同,附加功能可實現更高的精確度,並提供操作選項。這允許srgn
沿著正則表達式維度進行操作,而 IDE 工具(重命名全部、查找所有引用...)單獨無法操作,這是對它們的補充。
srgn
圍繞著要採取的操作(如果有)進行組織,僅在精確的、可選的語言語法感知範圍內起作用。就現有工具而言,將其視為tr
、 sed
、 ripgrep 和tree-sitter
的混合體,其設計目標是簡單性:如果您了解正則表達式和您正在使用的語言的基礎知識,那麼您就可以開始使用。
提示
此處顯示的所有程式碼片段均作為單元測試的一部分使用實際的srgn
進位檔案進行驗證。這裡展示的內容保證有效。
最簡單的srgn
用法與tr
類似:
$ echo ' Hello World! ' | srgn ' [wW]orld ' ' there ' # replacement
Hello there !
正規表示式模式'[wW]orld'
(範圍)的匹配項被第二個位置參數取代(操作)。可以指定零個或多個操作:
$ echo ' Hello World! ' | srgn ' [wW]orld ' # zero actions: input returned unchanged
Hello World !
$ echo ' Hello World! ' | srgn --upper ' [wW]orld ' ' you ' # two actions: replacement, afterwards uppercasing
Hello YOU !
替換始終首先執行並按位置指定。任何其他操作都會在命令列標誌之後應用並作為命令列標誌給出。
類似地,可以指定多個範圍:除了正規表示式模式之外,還可以指定語言語法感知範圍,其範圍為原始程式碼的語法元素(例如,「Python 中class
定義的所有主體」) )。如果兩者都給定,則正規表示式模式僅在第一語言範圍內應用。這使得使用普通正規表示式通常不可能實現的精確搜尋和操作,並提供與 IDE 中的「重新命名全部」等工具不同的維度。
例如,考慮這個(毫無意義的)Python 原始檔:
"""Module for watching birds and their age."""
from dataclasses import dataclass
@ dataclass
class Bird :
"""A bird!"""
name : str
age : int
def celebrate_birthday ( self ):
print ( "?" )
self . age += 1
@ classmethod
def from_egg ( egg ):
"""Create a bird from an egg."""
pass # No bird here yet!
def register_bird ( bird : Bird , db : Db ) -> None :
assert bird . age >= 0
with db . tx () as tx :
tx . insert ( bird )
可以使用以下方式進行搜尋:
$ cat birds.py | srgn --python ' class ' ' age '
11: age: int
15: self.age += 1
字串age
僅在Python class
定義中尋找和找到(而不是在諸如register_bird
之類的函數體中,其中age
也出現,並且在普通grep
中幾乎不可能排除在考慮範圍之外)。預設情況下,此「搜尋模式」也會列印行號。如果未指定任何操作,則進入搜尋模式,並且給定諸如--python
之類的語言1 — 將其想像為「ripgrep,但具有句法語言元素」。
搜尋也可以跨行執行,例如尋找缺少文件字串的方法(又稱class
內的def
):
$ cat birds.py | srgn --python ' class ' ' def .+:ns+[^"s]{3} ' # do not try this pattern at home
13: def celebrate_birthday(self):
14: print("?")
請注意,它不會顯示from_egg
(具有文件字串)或register_bird
(不是方法, def
external class
)。
語言範圍本身也可以指定多次。例如,在 Rust 程式碼片段中
pub enum Genre {
Rock ( Subgenre ) ,
Jazz ,
}
const MOST_POPULAR_SUBGENRE : Subgenre = Subgenre :: Something ;
pub struct Musician {
name : String ,
genres : Vec < Subgenre > ,
}
可以透過手術將多個項目鑽入
$ cat music.rs | srgn --rust ' pub-enum ' --rust ' type-identifier ' ' Subgenre ' # AND'ed together
2: Rock(Subgenre),
其中僅傳回符合所有條件的行,其作用類似於所有條件之間的邏輯與。請注意,條件是從左到右計算的,從而排除了某些組合的意義:例如,在Python doc-strings
中搜尋 Python class
主體通常不會傳回任何內容。然而,逆過程卻如預期般運作:
$ cat birds.py | srgn --py ' class ' --py ' doc-strings '
8: """A bird!"""
19: """Create a bird from an egg."""
class
之外沒有任何文件字串出現!
-j
標誌改變了這種行為:從左到右交叉,到獨立運行所有查詢並連接它們的結果,允許您一次搜尋多種方式:
$ cat birds.py | srgn -j --python ' comments ' --python ' doc-strings ' ' bird[^s] '
8: """A bird!"""
19: """Create a bird from an egg."""
20: pass # No bird here yet!
同樣, bird[^s]
模式也可以在註解或文件字串中找到,而不僅僅是「註解中的文件字串」。
如果未給予標準輸入, srgn
知道如何自動尋找相關來源文件,例如在此儲存庫中:
$ srgn --python ' class ' ' age '
docs/samples/birds
11: age: int
15: self.age += 1
docs/samples/birds.py
9: age: int
13: self.age += 1
它遞歸地遍歷當前目錄,根據文件副檔名和 shebang 行查找文件,並以非常高的速度進行處理。例如, srgn --go strings 'd+'
可在 3 秒內在 M3 的 12 個核心上尋找並列印約 3,000,000 行 Go 程式碼的 Kubernetes 程式碼庫中 Go 字串中的所有約 140,000 個數字。有關處理多個文件的更多信息,請參見下文。
範圍和操作幾乎可以任意組合(儘管許多組合不會有用,甚至沒有意義)。例如,考慮這個 Python 片段(有關使用其他支援的語言的範例,請參見下文):
"""GNU module."""
def GNU_says_moo ():
"""The GNU function -> say moo -> ✅"""
GNU = """
GNU
""" # the GNU...
print ( GNU + " says moo" ) # ...says moo
運行以下命令:
cat gnu.py | srgn --titlecase --python ' doc-strings ' ' (?' ' $1: GNU ? is not Unix '
該調用的剖析是:
--titlecase
(一個動作)將在範圍內找到的所有內容命名為標題
--python 'doc-strings'
(範圍)將根據 Python 語言語法將範圍限定為(即,僅考慮)文件字串
'(? (範圍)只看到前一個選項已經確定的範圍,並將進一步縮小範圍。它永遠不能擴大以前的範圍。正規表示式範圍應用於任何語言範圍之後。
(?是否定後向語法,示範如何使用此進階功能。不考慮以
The
為前綴的GNU
字串。
'$1: GNU ? is not Unix'
(一個操作)將用該字串取代每個符合的事件(即,發現在範圍內的每個輸入部分)。匹配的出現是僅在 Python 文件字串中的'(?模式。值得注意的是,這個替換字串顯示:
$1
進行動態變數綁定和替換,它攜帶第一個捕獲正規表示式群組捕獲的內容。那是([az]+)
,因為(?沒有捕獲。
此命令使用多個範圍(語言和正規表示式模式)和多個操作(替換和標題大小寫)。然後結果顯示為
"""Module: GNU ? Is Not Unix."""
def GNU_says_moo ():
"""The GNU function -> say moo -> ✅"""
GNU = """
GNU
""" # the GNU...
print ( GNU + " says moo" ) # ...says moo
其中更改僅限於:
- """GNU module."""
+ """Module: GNU ? Is Not Unix."""
def GNU_says_moo():
"""The GNU -> say moo -> ✅"""
警告
雖然srgn
處於測試版(主要版本 0),但請確保僅(遞歸地)處理可以安全復原的檔案。
搜尋模式不會覆蓋文件,因此始終安全。
有關該工具的完整幫助輸出,請參閱下文。
筆記
支援的語言有
從版本下載預先建置的二進位。
該板條箱以與cargo-binstall
相容的格式提供其二進位檔案:
cargo install cargo-binstall
(可能需要一段時間)cargo binstall srgn
(幾秒鐘,因為它從 GitHub 下載預先建置的二進位檔案)這些步驟保證有效™,因為它們在 CI 中進行了測試。如果您的平台沒有可用的預先建置二進位文件,它們也可以工作,因為該工具將退回到從原始程式碼進行編譯。
可以透過以下方式獲得公式:
brew install srgn
透過不穩定可用:
nix-shell -p srgn
可透過 AUR 取得。
連接埠可用:
sudo port install srgn
所有 GitHub Actions 運行器鏡像都預先安裝了cargo
,而cargo-binstall
提供了方便的 GitHub Action:
jobs :
srgn :
name : Install srgn in CI
# All three major OSes work
runs-on : ubuntu-latest
steps :
- uses : cargo-bins/cargo-binstall@main
- name : Install binary
run : >
cargo binstall
--no-confirm
srgn
- name : Use binary
run : srgn --version
上面的內容總共只花了 5 秒就結束了,因為不需要編譯。有關更多上下文,請參閱cargo-binstall
關於 CI 的建議。
在 Linux 上, gcc
可以運作。
在 macOS 上,使用clang
。
在 Windows 上,MSVC 可以工作。
安裝時選擇「使用 C++ 進行桌面開發」。
cargo install srgn
cargo add srgn
請參閱此處以了解更多資訊。
shell 完成腳本支援各種 shell。例如,將eval "$(srgn --completions zsh)"
附加到~/.zshrc
以在 ZSH 中完成補全。互動式會話可以如下所示:
該工具是圍繞範圍和操作設計的。範圍縮小了要處理的輸入部分。然後操作執行處理。通常,作用域和操作都是可組合的,因此可以傳遞其中的多個。兩者都是可選的(但不採取任何行動是沒有意義的);不指定範圍意味著整個輸入都在範圍內。
同時,與普通tr
存在相當大的重疊:該工具被設計為在最常見的用例中具有密切的對應性,並且僅在需要時才進行超越。
最簡單的動作就是替換。它是為了與tr
和一般人體工學兼容而專門訪問的(作為參數,而不是選項)。所有其他操作都作為標誌或選項(如果它們具有值)給出。
例如,簡單的單字元替換如tr
中所示:
$ echo ' Hello, World! ' | srgn ' H ' ' J '
Jello, World!
第一個參數是範圍(在本例中為文字H
)。與它相符的任何內容都將受到處理(在本例中替換為J
,即第二個參數)。但是,沒有像tr
中那樣直接的字元類別概念。相反,預設情況下,範圍是正規表示式模式,因此它的類別可以用於類似的效果:
$ echo ' Hello, World! ' | srgn ' [a-z] ' ' _ '
H____, W____!
預設情況下,替換會在整個匹配中貪婪地發生(注意 UTS 字元類,讓人想起tr
的[:alnum:]
):
$ echo ' ghp_oHn0As3cr3T!! ' | srgn ' ghp_[[:alnum:]]+ ' ' * ' # A GitHub token
*!!
支援高級正規表示式功能,例如環視:
$ echo ' ghp_oHn0As3cr3T ' | srgn ' (?<=ghp_)[[:alnum:]]+ ' ' * '
ghp_*
安全使用這些模式時要小心,因為高級模式沒有一定的安全性和性能保證。如果不使用它們,性能不會受到影響。
替換不限於單一字元。它可以是任何字串,例如修復此引用:
$ echo ' "Using regex, I now have no issues." ' | srgn ' no issues ' ' 2 problems '
"Using regex, I now have 2 problems."
該工具完全支援 Unicode,對某些高級字元類別提供有用的支援:
$ echo ' Mood: ? ' | srgn ' ? ' ' ? '
Mood: ?
$ echo ' Mood: ???? :( ' | srgn ' p{Emoji_Presentation} ' ' ? '
Mood: ???? :(
替換可以識別變量,可以透過正規表示式擷取群組存取這些變數。捕獲組可以編號,也可以選擇命名。第零個捕獲組對應於整個比賽。
$ echo ' Swap It ' | srgn ' (w+) (w+) ' ' $2 $1 ' # Regular, numbered
It Swap
$ echo ' Swap It ' | srgn ' (w+) (w+) ' ' $2 $1$1$1 ' # Use as many times as you'd like
It SwapSwapSwap
$ echo ' Call +1-206-555-0100! ' | srgn ' Call (+?d-d{3}-d{3}-d{4}).+ ' ' The phone number in "$0" is: $1. ' # Variable `0` is the entire match
The phone number in "Call +1-206-555-0100!" is: +1-206-555-0100.
例如,一個更高級的用例是使用命名捕獲組進行程式碼重構(也許您可以想出一個更有用的...):
$ echo ' let x = 3; ' | srgn ' let (?[a-z]+) = (?.+); ' ' const $var$var = $expr + $expr; '
const xx = 3 + 3;
與 bash 中一樣,使用花括號來消除緊鄰內容中的變數的歧義:
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2${1}1 '
211
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2$11 ' # will fail (`11` is unknown)
$ echo ' 12 ' | srgn ' (d)(d) ' ' $2${11 ' # will fail (brace was not closed)
看到替換只是一個靜態字串,它的用處是有限的。這就是tr
的秘密武器通常發揮作用的地方:使用它的字符類,這些字符類在第二個位置也有效,巧妙地從第一個位置的成員轉換為第二個位置的成員。在這裡,這些類別是正規表示式,並且僅在第一個位置(範圍)有效。正規表示式是狀態機,不可能匹配到“字元列表”,而“字元列表”在tr
中是第二個(可選)參數。這個概念已經被拋之腦後,並且失去了彈性。
相反,使用所提供的全部固定的操作。瀏覽一下tr
最常見的用例就會發現,所提供的一組操作幾乎涵蓋了所有這些!如果您的用例未涵蓋,請隨時提出問題。
進行下一步行動。
刪除從輸入中找到的所有內容。與tr
中的標誌名稱相同。
$ echo ' Hello, World! ' | srgn -d ' (H|W|!) '
ello, orld
筆記
由於預設範圍是匹配整個輸入,因此在沒有範圍的情況下指定刪除是錯誤的。
將與範圍匹配的重複字元壓縮為單一出現的字元。與tr
中的標誌名稱相同。
$ echo ' Helloooo Woooorld!!! ' | srgn -s ' (o|!) '
Hello World!
如果傳遞了一個字元類,則該類別的所有成員都將被擠入首先遇到的任何類別成員中:
$ echo ' The number is: 3490834 ' | srgn -s ' d '
The number is: 3
配對中的貪婪性不會被修改,所以要小心:
$ echo ' Winter is coming... ??? ' | srgn -s ' ?+ '
Winter is coming... ???
筆記
該圖案與整個太陽的運行相匹配,因此沒有什麼可以擠壓的。夏季盛行。
如果用例需要,則反轉貪婪:
$ echo ' Winter is coming... ??? ' | srgn -s ' ?+? ' ' ☃️ '
Winter is coming... ☃️
筆記
同樣,與刪除一樣,在沒有明確範圍的情況下指定壓縮也是錯誤。否則,整個輸入都會被壓縮。
tr
使用的很大一部分屬於這一類。這非常簡單。
$ echo ' Hello, World! ' | srgn --lower
hello, world!
$ echo ' Hello, World! ' | srgn --upper
HELLO, WORLD!
$ echo ' hello, world! ' | srgn --titlecase
Hello, World!
根據規範化形式 D 分解輸入,然後丟棄標記類別的程式碼點(請參閱範例)。這大致意味著:選擇花哨的角色,撕掉懸掛的部分,然後扔掉它們。
$ echo ' Naïve jalapeño ärgert mgła ' | srgn -d ' P{ASCII} ' # Naive approach
Nave jalapeo rgert mga
$ echo ' Naïve jalapeño ärgert mgła ' | srgn --normalize # Normalize is smarter
Naive jalapeno argert mgła
請注意, mgła
超出了 NFD 的範圍,因為它是“原子的”,因此不可分解(至少這是 ChatGPT 在我耳邊低聲說的)。
此操作將多字元 ASCII 符號替換為適當的單一代碼點、本機 Unicode 對應項。
$ echo ' (A --> B) != C --- obviously ' | srgn --symbols
(A ⟶ B) ≠ C — obviously
或者,如果您只對數學有興趣,請使用範圍界定:
$ echo ' A <= B --- More is--obviously--possible ' | srgn --symbols ' <= '
A ≤ B --- More is--obviously--possible
由於 ASCII 符號與其替換符號之間存在 1:1 對應關係,因此效果是可逆的2 :
$ echo ' A ⇒ B ' | srgn --symbols --invert
A => B
目前僅支援有限的符號集,但可以添加更多符號。
此操作將德語特殊字元 (ae, oe, ue, ss) 的替代拼寫替換為其本機版本 (ä, ö, ü, ß) 3 。
$ echo ' Gruess Gott, Neueroeffnungen, Poeten und Abenteuergruetze! ' | srgn --german
Grüß Gott, Neueröffnungen, Poeten und Abenteuergrütze!
此操作基於單字清單(如果這使您的二進位過於膨脹,則編譯時不使用german
功能)。請注意上述範例的以下特點:
Poeten
保持原樣,而不是天真地錯誤地轉換為Pöten
Abenteuergrütze
不會出現在任何合理的單字清單中,但仍然得到了正確的處理Abenteuer
也保持原樣,而不是錯誤地轉換為Abenteür
Neueroeffnungen
偷偷地形成了一個組成詞( neu
、 Eröffnungen
)都不具備的ue
元素,但仍然被正確處理(儘管大小寫也不匹配)根據要求,可能會強制進行替換,這對於名稱可能有用:
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ '
Frau Lötter steht ueber der Mauer.
透過正向的前瞻,除了稱呼之外什麼都沒有被限定,因此也改變了。 Mauer
正確地保持原樣,但ueber
未經過處理。第二遍解決了這個問題:
$ echo ' Frau Loetter steht ueber der Mauer. ' | srgn --german-naive ' (?<=Frau )w+ ' | srgn --german
Frau Lötter steht über der Mauer.
筆記
與某些「父級」相關的選項和標誌以其父級名稱為前綴,並且在給出時將暗示其父級,以便後者不需要明確傳遞。這就是為什麼--german-naive
被命名為 --german-naive,而--german
不需要被傳遞。
一旦clap
支援子命令鏈接,這種行為可能會改變。
對於這個不起眼的工具來說,某些分支是無法確定的,因為它在沒有語言上下文的情況下運作。例如, Busse
(公車)和Buße
(懺悔)都是合法字。預設情況下,如果合法,替換就會貪婪地執行(畢竟這就是srgn
的全部意義),但是有一個標誌用於切換此行為:
$ echo ' Busse und Geluebte ' | srgn --german
Buße und Gelübte
$ echo ' Busse ? und Fussgaenger ?♀️ ' | srgn --german-prefer-original
Busse ? und Fußgänger ?♀️
大多數操作都是可組合的,除非這樣做是無意義的(例如刪除)。它們的應用順序是固定的,因此給定標誌的順序沒有影響(如果需要,管道多次運行是一種替代方案)。替換總是首先發生。一般來說,CLI 的設計是為了防止誤用和意外:它寧願崩潰也不願意做一些意想不到的事情(當然,這是主觀的)。請注意,許多組合在技術上都是可行的,但可能會產生無意義的結果。
組合操作可能如下所示:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu
KOEFFIZIENTEN ≠ BRÜCKEN...
可以指定更窄的範圍,並將同樣適用於所有操作:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu ' bw{1,8}b '
Koeffizienten != BRÜCKEN...
需要單字邊界,否則Koeffizienten
會配對為Koeffizi
和enten
。請注意尾隨期間如何不能被壓縮等。 .
會幹擾給定的。常規管道解決了這個問題:
$ echo ' Koeffizienten != Bruecken... ' | srgn -Sgu ' bw{1,8}b ' | srgn -s ' . '
Koeffizienten != BRÜCKEN.
注意:可以使用文字範圍來規避正規表示式轉義( .
)。經過特殊處理的替換動作也是可組合的:
$ echo ' Mooood: ????!!! ' | srgn -s ' p{Emoji} ' ' ? '
Mooood: ?!!!
表情符號首先被全部替換,然後被擠壓。注意沒有其他東西是如何被擠壓的。
範圍是srgn
的第二個驅動概念。在預設情況下,主要範圍是正規表示式。操作部分詳細展示了此用例,因此此處不再重複。它作為第一個位置參數給出。
srgn
透過準備好的、語言語法感知的範圍擴展了這一點,這透過優秀的tree-sitter
函式庫成為可能。它提供了查詢功能,其工作原理非常類似於針對樹資料結構的模式匹配。
srgn
與這些查詢中的一些最有用的查詢捆綁在一起。透過其可發現的 API(作為庫或透過 CLI, srgn --help
),人們可以了解支援的語言和可用的、準備好的查詢。每種支援的語言都帶有逃生艙口,可讓您執行自己的自訂即席查詢。該陰影以--lang-query
的形式出現,其中lang
是一種語言,例如python
。有關此高級主題的更多信息,請參閱下文。
筆記
首先應用語言範圍,因此無論您傳遞什麼正規表示式(又稱為主範圍),它都會單獨對每個匹配的語言構造進行操作。
本節顯示了一些準備好的查詢的範例。
unsafe
程式碼(Rust) Rust 中unsafe
關鍵字的優點之一是它的「grepability」。然而, rg 'unsafe'
當然會顯示所有字串匹配( rg 'bunsafeb'
在一定程度上有幫助),而不僅僅是實際 Rust 語言關鍵字中的那些。 srgn
有助於使這一點更加精確。例如:
// Oh no, an unsafe module!
mod scary_unsafe_operations {
pub unsafe fn unsafe_array_access ( arr : & [ i32 ] , index : usize ) -> i32 {
// UNSAFE: This function performs unsafe array access without bounds checking
* arr . get_unchecked ( index )
}
pub fn call_unsafe_function ( ) {
let unsafe_numbers = vec ! [ 1 , 2 , 3 , 4 , 5 ] ;
println ! ( "About to perform an unsafe operation!" ) ;
let result = unsafe {
// Calling an unsafe function
unsafe_array_access ( & unsafe_numbers , 10 )
} ;
println ! ( "Result of unsafe operation: {}" , result ) ;
}
}
可以搜尋為