sed:將整個文件讀入模式空間而不會在單行輸入上失敗
將整個文件讀入模式空間對於替換換行符等很有用。並且有許多實例建議以下內容:
sed ':a;N;$!ba; [commands...]'
但是,如果輸入僅包含一行,則會失敗。
例如,使用兩行輸入,每一行都受替換命令的影響:
$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g' xxxbc cxxxt
但是,對於單行輸入,不執行替換:
$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g' abc
如何編寫一個
sed
命令來一次讀取所有輸入而沒有這個問題?
將整個文件讀入模式空間可能出錯的原因有很多。圍繞最後一行的問題中的邏輯問題是一個常見問題。它與
sed
的行週期有關 - 當沒有更多行並sed
遇到 EOF 時,它會通過 - 它退出處理。因此,如果您在最後一行並且您指示sed
獲得另一行,它將停在那裡並且不再做任何事情。也就是說,如果您真的需要將整個文件讀入模式空間,那麼無論如何可能值得考慮另一種工具。事實是,與流
sed
編輯器同名- 它旨在一次工作一行 - 或一個邏輯數據塊。有許多類似的工具可以更好地處理完整的文件塊。
ed
和ex
,例如,可以sed
用類似的語法做很多可以做的事情——除此之外還有很多其他事情——但不是只對輸入流進行操作,同時將其轉換為輸出sed
,它們還在文件系統中維護臨時備份文件. 他們的工作會根據需要緩衝到磁碟上,並且不會在文件結束時突然退出*(並且在緩沖壓力下往往很少發生內爆)*。此外,它們提供了許多有用的功能,這些功能sed
在流上下文中根本沒有意義,例如行標記、撤消、命名緩衝區、連接等。
sed
的主要優勢在於它能夠在讀取數據後立即處理數據 - 快速、高效和流式傳輸。當你吃掉一個文件時,你會把它扔掉*,你往往會遇到邊緣情況困難,比如你提到的最後一行問題、緩衝區溢出和糟糕的性能——隨著它解析的數據長度增長,正則表達式引擎在列舉匹配項時的處理時間成倍*增加。關於最後一點,順便說一下:雖然我理解範例
s/a/A/g
案例很可能只是一個幼稚的範例,並且可能不是您想要在輸入中收集的實際腳本,但您可能會發現值得您花時間熟悉y///
. 如果您經常發現自己g
用一個字元替換另一個字元,那麼y
這對您可能非常有用。它是一種轉換而不是替換,並且速度更快,因為它並不意味著正則表達式。後一點在嘗試保留和重複空//
地址時也很有用,因為它不會影響它們,但會受到它們的影響。無論如何,y/a/A/
這是一種更簡單的方法來完成相同的任務 - 並且交換也是可能的,例如:y/aA/Aa/
這將交換所有大寫/小寫作為彼此在一條線上。您還應該注意,您描述的行為實際上並不是無論如何都應該發生的。
來自 GNU 的
info sed
COMMONLY REPORTED BUGS部分:
N
最後一行的命令
- 當在文件的最後一行發出命令時,大多數版本的
sed
退出而不列印任何內容。N
GNUsed
會在退出前列印模式空間,除非-n
指定了命令開關。這種選擇是設計使然。- 例如, 的行為
sed N foo bar
取決於 foo 的行數是偶數還是奇數。或者,當編寫腳本以讀取模式匹配後的接下來幾行時,傳統的實現sed
會迫使您編寫類似的東西/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
而不是僅僅/foo/{ N;N;N;N;N;N;N;N;N; }
.- 在任何情況下,最簡單的解決方法是
$d;N
在依賴傳統行為的腳本中使用,或者將POSIXLY_CORRECT
變數設置為非空值。
POSIXLY_CORRECT
提到環境變數是因為 POSIX 指定如果在sed
嘗試時遇到 EOF,N
它應該在不輸出的情況下退出,但 GNU 版本在這種情況下故意違反標準。還要注意,即使上述行為是合理的,假設錯誤情況是流編輯之一 - 不是將整個文件放入記憶體中。該標准定義了
N
’ 的行為,因此:
N
- 將下一行輸入(減去其終止
\n
ewline)附加到模式空間,使用嵌入的\n
ewline 將附加的材料與原始材料分開。請注意,目前行號會發生變化。- 如果沒有可用的下一行輸入,
N
命令動詞將跳轉到腳本的末尾並退出,而不開始新的循環或將模式空間複製到標準輸出。關於這一點,問題中還展示了其他一些 GNU 主義——特別是
:
標籤、b
牧場和{
函式上下文括號的使用}
。根據經驗,任何sed
接受任意參數的命令都被理解為\n
在腳本中的 ewline 處進行分隔。所以命令…:arbitrary_label_name; ... b to_arbitrary_label_name; ... //{ do arbitrary list of commands } ...
sed
…根據讀取它們的實現,都很可能執行不規律。可移植的應該寫成:...;:arbitrary_label_name ...;b to_arbitrary_label_name //{ do arbitrary list of commands }
r
,w
,t
,a
,i
, 和c
(可能還有一些我現在忘記的)也是如此。幾乎在每種情況下,它們也可以寫成:sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \ "//{ do arbitrary list of commands" -e \}
…新的
-e
執行語句代表\n
ewline 分隔符。因此,如果 GNUinfo
文本建議傳統sed
實現將迫使您這樣做:/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
……應該是……
/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }
……當然,這也不是真的。這樣寫劇本有點傻。還有更簡單的方法可以做到這一點,例如:
printf %s\\n foo . . . . . . | sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd //!g;x;$!d;:nd' -e 'l;$a\' \ -e 'this is the last line'
…列印:
foo . . . foo\n.\n.\n.$ .$ this is the last line
…因為
t
est 命令 - 像大多數sed
命令一樣 - 依賴於行週期來刷新其返回寄存器,並且這裡允許行週期完成大部分工作。這是您在 slurp 文件時所做的另一個權衡 - 行週期不會再次刷新,因此許多測試會表現異常。上面的命令不會冒過度輸入的風險,因為它只是做一些簡單的測試來驗證它讀取的內容。
H
舊的所有行都附加到保留空間,但如果一行匹配,/foo/
它將覆蓋h
舊空間。接下來x
更改緩衝區,如果緩衝區的內容與最後定址的模式s///
匹配,則嘗試進行條件替換。//
換句話說//s/\n/&/3p
,嘗試用自己替換保持空間中的第三個換行符,並在保持空間目前匹配時列印結果/foo/
。如果t
測試成功,則腳本分支到n
otd
elete 標籤 - 它會執行操作l
並包裝腳本。如果兩個
/foo/
換行符和第三個換行符在保持空間中不能一起匹配,則//!g
如果不匹配,則將覆蓋緩衝區,或者,如果匹配,則如果ewline 不匹配/foo/
,它將覆蓋緩衝區*(從而替換**為本身)。這個微妙的小測試可以防止緩衝區在長時間的“否”中不必要地填滿,並確保該過程保持快速,因為輸入不會堆積。在沒有或失敗的情況下,緩衝區再次被交換,並且除最後一行之外的每一行都被刪除。\n
/foo/
*/foo/``/foo/``//s/\n/&/3p
最後一行 -最後一行
$!d
- 是一個簡單的展示,展示瞭如何製作一個自上而下的sed
腳本來輕鬆處理多個案例。當您的一般方法是從最一般的情況開始並朝著最具體的方向努力時,可以更輕鬆地處理邊緣情況,因為它們可以簡單地與您的其他想要的數據一起落到腳本的末尾,並且何時這一切都包裹著你,只剩下你想要的數據。但是,必須從閉環中提取此類邊緣情況可能要困難得多。所以這是我要說的最後一件事:如果你真的必須拉入整個文件,那麼你可以依靠行循環為你做更少的工作。通常,您會使用
N
ext 和n
ext 進行前瞻——因為它們提前於行週期。而不是在循環中冗餘地實現一個閉環 - 因為sed
行循環只是一個簡單的讀取循環 - 如果您的目的只是不加選擇地收集輸入,那麼它可能更容易做到:sed 'H;1h;$!d;x;...'
…這將收集整個文件或嘗試失敗。
N
關於最後一行行為的旁注……雖然我沒有可供我測試的工具,但請考慮如果編輯的文件是下一次通讀的腳本文件,則
N
在讀取和就地編輯時的行為會有所不同。