Sed

sed:將整個文件讀入模式空間而不會在單行輸入上失敗

  • September 21, 2021

將整個文件讀入模式空間對於替換換行符等很有用。並且有許多實例建議以下內容:

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編輯器同名- 它旨在一次工作一行 - 或一個邏輯數據塊。

有許多類似的工具可以更好地處理完整的文件塊。edex,例如,可以sed用類似的語法做很多可以做的事情——除此之外還有很多其他事情——但不是只對輸入流進行操作,同時將其轉換為輸出sed,它們還在文件系統中維護臨時備份文件. 他們的工作會根據需要緩衝到磁碟上,並且不會在文件結束時突然退出*(並且在緩沖壓力下往往很少發生內爆)*。此外,它們提供了許多有用的功能,這些功能sed在流上下文中根本沒有意義,例如行標記、撤消、命名緩衝區、連接等。

sed的主要優勢在於它能夠在讀取數據後立即處理數據 - 快速、高效和流式傳輸。當你吃掉一個文件時,你會把它扔掉*,你往往會遇到邊緣情況困難,比如你提到的最後一行問題、緩衝區溢出和糟糕的性能——隨著它解析的數據長度增長,正則表達式引擎在列舉匹配項時的處理時間成倍*增加。

關於最後一點,順便說一下:雖然我理解範例s/a/A/g案例很可能只是一個幼稚的範例,並且可能不是您想要在輸入中收集的實際腳本,但您可能會發現值得您花時間熟悉y///. 如果您經常發現自己g用一個字元替換另一個字元,那麼y這對您可能非常有用。它是一種轉換而不是替換,並且速度更快,因為它並不意味著正則表達式。後一點在嘗試保留和重複空//地址時也很有用,因為它不會影響它們,但會受到它們的影響。無論如何,y/a/A/這是一種更簡單的方法來完成相同的任務 - 並且交換也是可能的,例如:y/aA/Aa/這將交換所有大寫/小寫作為彼此在一條線上。

您還應該注意,您描述的行為實際上並不是無論如何都應該發生的。

來自 GNU 的info sedCOMMONLY REPORTED BUGS部分:

  • N最後一行的命令

    • 當在文件的最後一行發出命令時,大多數版本的sed退出而不列印任何內容。NGNUsed會在退出前列印模式空間,除非-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

    • 將下一行輸入(減去其終止\newline)附加到模式空間,使用嵌入的\newline 將附加的材料與原始材料分開。請注意,目前行號會發生變化。
    • 如果沒有可用的下一行輸入,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執行語句代表\newline 分隔符。因此,如果 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

…因為test 命令 - 像大多數sed命令一樣 - 依賴於行週期來刷新其返回寄存器,並且這裡允許行週期完成大部分工作。這是您在 slurp 文件時所做的另一個權衡 - 行週期不會再次刷新,因此許多測試會表現異常。

上面的命令不會冒過度輸入的風險,因為它只是做一些簡單的測試來驗證它讀取的內容。H舊的所有行都附加到保留空間,但如果一行匹配,/foo/它將覆蓋h舊空間。接下來x更改緩衝區,如果緩衝區的內容與最後定址的模式s///匹配,則嘗試進行條件替換。//換句話說//s/\n/&/3p,嘗試用自己替換保持空間中的第三個換行符,並在保持空間目前匹配時列印結果/foo/。如果t測試成功,則腳本分支到not delete 標籤 - 它會執行操作l並包裝腳本。

如果兩個/foo/換行符和第三個換行符在保持空間中不能一起匹配,則//!g如果不匹配,則將覆蓋緩衝區,或者,如果匹配,則如果ewline 不匹配/foo/,它將覆蓋緩衝區*(從而替換**為本身)。這個微妙的小測試可以防止緩衝區在長時間的“否”中不必要地填滿,並確保該過程保持快速,因為輸入不會堆積。在沒有或失敗的情況下,緩衝區再次被交換,並且除最後一行之外的每一行都被刪除。\n/foo/*/foo/``/foo/``//s/\n/&/3p

最後一行 -最後一行$!d- 是一個簡單的展示,展示瞭如何製作一個自上而下的sed腳本來輕鬆處理多個案例。當您的一般方法是從最一般的情況開始並朝著最具體的方向努力時,可以更輕鬆地處理邊緣情況,因為它們可以簡單地與您的其他想要的數據一起落到腳本的末尾,並且何時這一切都包裹著你,只剩下你想要的數據。但是,必須從閉環中提取此類邊緣情況可能要困難得多。

所以這是我要說的最後一件事:如果你真的必須拉入整個文件,那麼你可以依靠行循環為你做更少的工作。通常,您會使用Next 和next 進行前瞻——因為它們提前行週期。而不是在循環中冗餘地實現一個閉環 - 因為sed行循環只是一個簡單的讀取循環 - 如果您的目的只是不加選擇地收集輸入,那麼它可能更容易做到:

sed 'H;1h;$!d;x;...'

…這將收集整個文件或嘗試失敗。


N關於最後一行行為的旁注……

雖然我沒有可供我測試的工具,但請考慮如果編輯的文件是下一次通讀的腳本文件,則N在讀取和就地編輯時的行為會有所不同。

引用自:https://unix.stackexchange.com/questions/182153