Linux技巧:sed命令替換、刪除、同時匹配多個模式的方法


Linux技巧:sed命令替換、刪除、同時匹配多個模式的方法

本篇文章介紹 Linux 系統上的 sed 命令用法,包括下面的內容:

  • 尋址和刪除操作
  • 替換操作
  • 在 sed 中引用 shell 變量
  • 只打印特定匹配的行
  • 同時匹配多個模式

在 Linux 中,sed 命令的完整格式如下:

<code>sed [OPTION]... {script-only-if-no-other-script} [input-file].../<code>

修改輸入文件本身的內容

sed 命令是一個流編輯器 (stream editor),可以對輸入的文本內容進行處理。所處理的文本內容可來自文件或者管道。

它默認把處理後的結果打印到標準輸出,不修改文件本身內容。

在 man sed 裡面沒有具體說明 sed 命令的處理結果會輸出到哪裡。

查看 GNU sed 的在線幫助說明,提到會打印到標準輸出:https://www.gnu.org/software/sed/manual/sed.html#output

sed writes output to standard output.

Use -i to edit files in-place instead of printing to standard output.

See also the W and s///w commands for writing output to other files.

The following command modifies file.txt and does not produce any output:

<code>    sed -i 's/hello/world/' file.txt/<code>

即, sed 默認不會把處理結果寫入到所給文件,如果要修改文件本身內容,要加 -i 選項。

尋址 Addresses

在 man sed 中,描述了 sed 命令如何選擇要操作的行:

Sed commands can be given with no addresses, in which case the command will be executed for all input lines; with one address, in which case the command will only be executed for input lines which match that address; or with two addresses, in which case the command will be executed for all input lines which match the inclusive range of lines starting from the first address and continuing to the second address.

部分例子如下:

  • number: Match only the specified line number. 直接用數字指定要操作的行數
  • /regexp/: Match lines matching the regular expression regexp.
  • $: Match the last line.

刪除操作

sed 使用 d 命令來刪除指定的行,man sed 的說明如下:

d

Delete pattern space. Start next cycle.

下面是幾個使用 d 命令從輸出結果中刪除某些行的例子:

  • 在輸出結果中不打印filename文件的第一行:
<code>    sed '1d' filename
/<code>
  • 在輸出結果中不打印filename文件的最後一行,下面的 $ 表示匹配最後一行:
<code>    sed '$d' filename
/<code>
  • 在輸出結果中不打印filename文件內所有包含 xml 字符串的行:
<code>    sed '/xml/d' filename
/<code>
<code>或者把 d 命令寫在單引號外面/<code>
<code>    sed '/xml/'d filename
/<code>

注意:寫為 sed '/*xml*/d' filename 將匹配不到包含 xml 字符串的行。這跟 '*' 這個字符在通配符和正則表達式之間的差異有關。sed 命令使用正則表達式來匹配模式,而在正則表達式中,'*'表示匹配零個或任意多個前面的字符,而不是表示匹配任意字符串。

  • 在輸出結果中不打印只有一個換行符的空行:
<code>    sed '/^$/d' filename
/<code>

^ 表示匹配行首,$ 表示匹配行末,在行首和行末之間沒有任何字符,也就是空行。嚴格來說,這裡說的“行末”指的是最後一個換行符前面的一個字符,不包括換行符自身。“空行” 實際上還是包含有一個換行符。

  • 在輸出結果中不打印由空白字符 (空格,製表符,換行符,回車符) 組成的行:
<code>    sed '/^[[:space:]]*$/d' filename
/<code>

這裡使用POSIX字符類[:space:]表示空白字符,把[:space:]放在[]裡面,會成為正則表達式,表示匹配在[]裡面的字符,後面跟了一個*,表示匹配0個或多個前面的字符,也就是匹配0個或多個空白字符。匹配到0個空白字符,就是匹配到只有換行符的空行。由於sed在處理時會先去掉行末的換行符,[:space]在這裡其實匹配不到行末的換行符,而是通過匹配到0個空白字符,相當於 /^$/d 的方式去掉空行。

注意

:這裡舉例的sed命令不會直接修改所給的 filename 文件本身的內容,只是用 d 命令從輸出結果中刪除匹配的行,如果要直接修改 filename 文件本身的內容,要加 -i 選項。

替換操作

sed 使用 s/regexp/replacement/ 命令來替換匹配特定模式的內容,man sed 的說明如下:

Attempt to match regexp against the pattern space.

If successful, replace that portion matched with replacement.

The replacement may contain the special character & to refer to that portion of the pattern space which matched, and the special escapes \\1 through \\9 to refer to the corresponding matching sub-expressions in the regexp.

舉例說明如下,下面的 sed 命令表示從標準輸入(也就是需要用戶輸入)讀取內容,把輸入的 foo 替換為 bar,再打印出來。在 # 後面的內容是註釋說明,不是命令內容的一部分。

$ sed s'/foo/bar/' # 從標準輸入接收用戶輸入, 把 foo 替換 bar 再輸出

afoo # 手動輸入 afoo, 然後回車

abar # 回車之後, sed 打印輸出結果, 替換顯示為 abar

afooF # foo 位於字符串的中間, 也會被替換

abarF

foo # 當然, 完整的 foo 也會被替換

bar

可以用這個替換命令來刪除行末的 '\\r' 字符:

<code>    sed -i 's/\\r//' filename
/<code>

Windows下的文本文件,每行的結尾是\\n\\r。而在Linux下,每行結尾只有\\n,那個多出來的\\r常常會導致一些問題,可以用這一個命令來去掉它。

在 sed 中引用 shell 變量

在 sed 裡面可以引用當前 shell 定義好的變量值,用 $ 來引用即可,但是有一些需要注意的地方:

  • 不能使用單引號把替換模式括起來,例如 '/pattern/command/' 要改成 "/pattern/commond". 因為在Bash shell裡面,單引號不支持擴展,$在單引號裡面還是表示'$'字符自身,並不表示獲取變量的值,無法用${param}來引用變量param的值。
  • 實際上是由 bash 自身通過 $ 來獲取變量值,進行變量擴展後,再把變量值作為參數傳遞給 sed 命令進行處理。這不是由 sed 命令自身來獲取 bash 定義的變量值。
  • 如果變量名後面沒有跟著其他字符,在變量名前後可以不加大括號{}。例如下面的sed命令獲取shell的pat變量的值,然後從輸出結果中去掉匹配pat變量值的行:
<code>    sed /$pat/d filename
/<code>
  • 在引用shell變量時,如果變量名後面跟著其他字符,要用{}把變量名括起來,避免變量名後面的字符被當成變量名的一部分。例如,有一個pat變量,那麼 $pat 獲取這個變量的值,${pat}A 表示在pat變量值後面還跟著一個字符A,但是 $patA 表示的是獲取名為 patA 的變量值。

只打印特定匹配的行

查看 GNU sed 的在線幫助鏈接:https://www.gnu.org/software/sed/manual/sed.html,裡面有如下說明:

By default sed prints all processed input (except input that has been modified/deleted by commands such as d).

Use -n to suppress output, and the p command to print specific lines. The following command prints only line 45 of the input file:

<code>    sed -n '45p' file.txt
/<code>

即,sed 默認會打印出被處理的輸入內容,這些內容跟原始輸入內容不一定完全一樣,sed 的一些命令可以修改或刪除輸入內容,再把新的內容打印出來。

打印的輸出結果並不是只對應匹配特定模式的行。

那些沒有被處理的行,會原樣打印。

如果只想打印匹配特定模式的行,要用 -n 選項和 p 命令。

例如,用下面命令只打印後綴名為 ".xml" 的內容:

<code>    sed -n '/\\.xml$/p'
/<code>

這裡使用 “\\.” 來轉義匹配 '.' 字符,用 '$' 來匹配行末。所以 “.xml$” 對應 “.xml” 後綴名。

上面貼出的 GNU sed 幫助鏈接對 -n 選項的具體說明如下:

-n

--quiet

--silent

By default, sed prints out the pattern space at the end of each cycle through the>

These options disable this automatic printing, and sed only produces output when explicitly told to via the p command.

查看 man sed 對 -n 選項的說明如下:

-n, --quiet, --silent

suppress automatic printing of pattern space

可以看到,man sed 中的說明比較簡略且含糊,而在線幫助鏈接的說明更容易理解,明確說明了 -n 選項避免自動打印 pattern space 的內容。

如果發現其他選項在 man 手冊中說明不清楚,可以再查看 GNU 在線手冊的說明。

注意:-n 選項並不表示打印匹配特定模式且被處理的行。

例如,使用 -n 選項和 d 命令不會看到任何打印,並不會打印出被刪除的行。

如果只是打印匹配特定模式的行,一般常用 grep 命令,但是 grep 命令不能對匹配的結果做二次處理,而 sed 命令可以做二次處理,打印特定匹配的行,且做一些修改。

例如下面的命令把後綴名為 ".cpp" 的行替換成 ".cc",且只打印出這些行:

<code>    sed -n 's/\\.cpp$/\\.cc/p'
/<code>

對該sed命令的各個參數說明如下:

  • "-n" 指定不自動打印pattern space的內容
  • 用 s 命令來進行替換
  • \\.cpp$ 表示匹配 ".cpp" 後綴名
  • \\.cc 是替換後的內容,也就是 ".cc"
  • 最後用 p 命令打印處理後的結果。例如輸入 main.cpp,會打印出 main.cc

注意:在 cc 後面不用加 $ 字符。在 cpp 後面加 $ 是為了匹配行末,$ 在這裡是正則表達式的元字符。如果在 cc 後面加 $,$ 會被當作普通字符,成為替換後的內容的一部分,也就是替換成 “.cc$”,這是不預期的。

上面的幫助鏈接對 sed 的 p 命令說明如下:

p

Print the pattern space.

從字面上看,p 命令就是打印 pattern space。關鍵是,pattern space具體是什麼

上面貼出的 GNU sed 幫助鏈接的 "6.1 How sed Works" 小節具體描述了 "pattern space" 的含義。

sed maintains two data buffers: the active pattern space, and the auxiliary hold space. Both are initially empty.

sed operates by performing the following cycle on each line of input: first, sed reads one line from the input stream, removes any trailing newline, and places it in the pattern space. Then commands are executed; each command can have an address associated to it: addresses are a kind of condition code, and a command is only executed if the condition is verified before the command is to be executed.

When the end of the>

基於這部分說明,pattern space 是一塊buffer,存放要處理的輸入行,sed 命令會對這一行進行處理,有些命令還會修改這一行的內容,處理結束後,可以用 p 命令打印 pattern space 裡面的內容。

由於這個內容可能會被修改,跟原始輸入行的內容不一定完全一樣。

例如,查看上面幫助鏈接對 s 命令的說明,就能看到 s 命令會修改pattern space的內容:

s/regexp/replacement/[flags]

(substitute) Match the regular-expression against the content of the pattern space.

If found, replace matched string with replacement.

同時匹配多個模式

在 sed 命令中可以使用多個 -e 選項來指定匹配多個模式:

<code>    sed -e '3,$d' -e 's/foo/bar/g'
/<code>

也可以使用 分號(;) 來分隔多個匹配項:

<code>    sed '3,$d; s/foo/bar/g'
/<code>

下面舉例說明這個方法的一個應用場景。

我們先用 find 命令查找當前目錄底下的所有文件名,現在想要從這些文件名中刪除後綴名為 ".rc"、".xml" 的文件名,具體寫法如下:

<code>    sed '/\\.rc$/d; /\\.xml$/d' filenames
/<code>

使用 \\. 來匹配後綴名前面的 '.' 字符,$ 表示匹配行末。所以 “\\.rc$” 對應 “.rc” 後綴名, “\\.xml$” 對應 “.xml” 後綴名。最後的 filenames 裡面保存查找到的所有文件名,作為 sed 要處理的輸入內容。

使用 man sed 查看 -e 選項的說明如下:

-e>

add the>

即,-e 指定執行後面跟著的命令,多個 -e 可以指定執行多個不同的命令。

在 man sed 中,沒有說明可以用分號(;)來分割多個匹配項,要查看 info sed 裡面的完整幫助手冊,才有相關描述。

如果不習慣查看info命令的內容格式,可以在網上在線查看幫助手冊,GNU sed home page: http://www.gnu.org/software/sed/

其中,HTML 版本的幫助手冊鏈接是:https://www.gnu.org/software/sed/manual/sed.html

該幫助鏈接對用分號(;)分割多個匹配項的說明如下,裡面還提到可以用換行符來分割。

Commands within a>

Commands a, c, i, due to their syntax, cannot be followed by semicolons working as command separators and thus should be terminated with newlines or be placed at the end of a>


分享到:


相關文章: