sed修煉系列(四):sed中的疑難雜症

来源:http://www.cnblogs.com/f-ck-need-u/archive/2017/09/09/7499309.html
-Advertisement-
Play Games

本文目錄:1 sed中使用變數和變數替換的問題2 反向引用失效問題3 "-i"選項的文件保存問題4 貪婪匹配問題5 sed命令"a"和"N"的糾葛 1.sed中使用變數和變數替換的問題 在腳本中使用sed的時候,很可能需要在sed中引用shell變數,甚至想在sed命令行中使用變數替換。也許很多人都 ...


本文目錄:
1 sed中使用變數和變數替換的問題
2 反向引用失效問題
3 "-i"選項的文件保存問題
4 貪婪匹配問題
5 sed命令"a"和"N"的糾葛

1.sed中使用變數和變數替換的問題

在腳本中使用sed的時候,很可能需要在sed中引用shell變數,甚至想在sed命令行中使用變數替換。也許很多人都遇到過這個問題,但引號卻死活調試不出正確的位置。其實這不是sed的問題,而是shell的特性。搞懂sed如何解決引號的問題,對理解shell引號問題有很大幫助,觸類旁通,以後在使用awk、mysql等等自帶語法解析的工具時就不會再疑惑。

例如下麵想輸出a.txt的倒數5行的語句。可能順手就寫出了下麵的命令行:

total=`wc -l <a.txt`
sed -n '$((total-4)),$p' a.txt

但很不幸,這會報錯。一方面,"$"在sed中是特殊符號,放在定址表達式中時,它表示的是輸入流的最後一行的標記。而$(())中也出現了"$"符號,這會讓sed去解析該符號。另一方面,$(())這部分是使用shell計算而不是使用sed計算的,因此必須要將其暴露給shell,以便能讓shell能解析它。

再說說shell中單引號、雙引號和不加引號的情況。

  • 單引號:單引號內的所有字元變為字面符號。但註意:單引號內不能再使用單引號,即使使用了反斜線轉義也不允許。
  • 雙引號:雙引號內的所有字元變為字面符號,但"\"、"$"、"`"(反引號)除外,如果開啟了"!"引用歷史命令時,則感嘆號也除外。
  • 不使用引號:等同於使用了雙引號。

上面關於雙引號的情況,描述的並不是真正的完整,但已足夠。這些只是它們的字面意義,引號真正的意義在於:決定命令行中哪些"單詞"需要被shell解析,也決定哪些是字面意義不用被shell解析詳細內容見:shell解析命令行的過程以及eval命令

顯然,單引號內所有字元都成為了字面符號,shell不會解析其內任何單詞,例如單引號內變數不再被解析、命令替換和算術運算不再執行、不會進行路徑擴展等等。總之,單引號內的字元全是普通字元,如果某些字元需要交給自帶解析功能的命令解析,必須使用單引號。例如,"$"、"!"和"{}"在sed中均有特殊意義,要想讓sed能解析它們,必須對它們使用單引號,否則必出錯,或者產生歧義。例如下麵3個sed語句中的符號都必須使用單引號才能得到正確結果。

sed '$d' filename
sed '1!d' filename
sed -n '2{p;q}' filename

而想要讓特殊字元被shell解析,必須不能將其包圍在單引號中,可以使用雙引號,也可以不加任何引號,即使不加引號時可能看上去很怪異。例如,上面的算術運算$(())是想被shell解析的,因此必須使用單引號或者不加引號將其暴露給shell。所以正確的語句是:

sed -n $((total-4))',$p' a.txt
sed -n "$((total-4))"',$p' a.txt
sed -n "$((total-4)),\$p" a.txt

從肉眼看上去,這個語句的引號加的真的很怪異。但shell又不管醜美,它是死的,在劃分命令行的時候它有自己的一套規則,規則怎樣就怎樣劃分。

於是,關於sed如何和shell交互的問題可以得出一套結論:

  1. 遇到需要被shell解析的都不加引號,或者加雙引號;
  2. 遇到shell和所執行命令共有的特殊字元時,要想被sed解析,必須加單引號,或者在雙引號在加反斜線轉義;
  3. 那些無關緊要的字元,無論加什麼引號。

因此,使用命令替換的方式讓sed輸出倒數5行的語句如下:

sed -n `expr $(wc -l <a.txt) - 4`',$p' a.txt

上面的語句中,`expr $(wc -l <a.txt) - 4` 要被shell解析,因此必須不能使用單引號包圍。而$p部分的"$"要被sed解析成最後一行,必須使用單引號以避免被shell解析。

更複雜一些,在sed的正則表達式中使用變數替換。例如,輸出a.txt中以變數str字元串開頭的行到最後一行。

str="abc"
sed -n /^$str/',$p' a.txt

因為沒有使用任何引號,所以$str能如期被shell替換成"abc"。這個命令還有多種寫法:

sed -n '/^'$str'/,$p' a.txt
sed -n "/^$str"'/,$p' a.txt
sed -n "/^$str/,\$p" a.txt
sed -n "/^$str/,"'$'p a.txt

給一個稍難一些的sed符號使用問題。將/etc/shadow中的最後一行的密碼部分替換成"$1$123456$wOSEtcyiP2N/IfIl15W6Z0"。

[root@xuexi ~]# tail -n 1 /etc/shadow
userX:$6$hS4yqJu7WQfGlk0M$Xj/SCS5z4BWSZKN0raNncu6VMuWdUVbDScMYxOgB7mXUj./dXJN0zADAXQUMg0CuWVRyZUu6npPLWoyv8eXPA.::0:99999:7:::

替換語句如下:

old_pass="$(tail -n 1 /etc/shadow | cut -d':' -f2)"
new_pass='$1$123456$wOSEtcyiP2N/IfIl15W6Z0'
sed -n '$'s%$old_pass%$new_pass% /etc/shadow

由於old_passold_pass中包含了"/"和"$"符號,因此"s"命令的分隔符使用了"%"替代。再仔細觀察new_pass,其內有"."符號,這是正則表達式的元字元,因此它還可以匹配其他情況。

2.反向引用失效問題

當正則表達式中使用二者選一的選項"|"時,如果分組括弧()中的內容沒有參與匹配,後向引用將不起作用。例如(a)\1u|b\1將只匹配"aau"的行,不匹配"ba"的行,因為在二者選一的第二個正則中\1代表的分組沒有參與匹配,所以第二個正則中的\1失效,但是第一個正則中的\1有效。

這是正則匹配的問題,不只是sed,其它使用基礎正則和擴展正則引擎的工具也一樣會有這樣的問題。

另外,在s命令中使用反向引用時,將不會引用"s"命令外面的分組。例如:

echo "ab3456cd" | sed -r "/(ab)/s/([0-9]+)/\1/"

得到的結果將是ab3456cd,而不是ababcd,而且如果此時使用\2引用,則會報錯"invalid reference \2 on 's' command's RHS"。

3."-i"選項的文件保存問題

sed是通過創建一個臨時文件,並將輸出寫入到該臨時文件,然後重命名該臨時文件為源文件來實現文件保存的。因此,sed會無視文件的只讀性。

是否允許重命名或移入或刪除文件,是由文件所在目錄的許可權控制的。如果目錄為只讀許可權,則sed無法使用"-i"選項保存結果,即使該文件具有可讀許可權。

4.貪婪匹配問題

所謂的貪婪匹配,是指當正則表達式能匹配多個內容時,取最長的那個。最簡單的例子,給定數據"abcdsbaz",正則表達式"a.*b"可以匹配該數據中"ab"和"abcdsb",由於貪婪匹配,它會取最長的"abcdsb"。

echo "abcdbaz" | grep -o "a.*b"
abcdb

基礎正則表達式和擴展正則表達式一直以來的一個不足之處在於無法原生態剋服貪婪匹配,像Perl正則或其他編程語言的正則實現的比較完整,在""或"+"這種多次重覆的匹配後加上一個"?"就可以明確表示採取懶惰匹配的模式,例如"a.?b"。

echo "abcdbaz" | grep -P -o "a.*?b"
ab

想要剋服基礎正則或擴展正則的貪婪匹配,只能"投機取巧"地採用不包含符號"[^]"來實現。例如上面的:

echo "abcdbaz" | grep -o "a[^b]*b" 
ab

這種投機取巧的方式,性能比較差,因為基礎或擴展正則表達式的引擎總是會先匹配出最長的內容,然後往回匹配,這稱為"回溯"。例如"abcdsbaz"在被"a[^b]*b"匹配時,先匹配出"abcdsb",再一個字元一個字元地回退匹配,直到回退到第一個"b"才是最短的結果。

再例如,/etc/passwd文件中每行數據的格式如下:

rootx:0:0:root:/root:/bin/bash

如何使用sed向/etc/passwd中的每個用戶問聲好,輸出格式大致為:"hello root"、"hello nobody"。

首先,得取出文件中的第一列,即用戶名。但由於該文件中所有行都採用冒號分隔各欄位,想要使用正則表達式匹配得到第一段,必須剋服貪婪匹配。語句如下:

sed -r 's/^([^:]*):.*/hello \1/' /etc/passwd

註意,sed採用的是基礎正則和擴展正則引擎,在剋服貪婪匹配時,它必須先匹配出最長的,再回溯出最短的。

如果想取/etc/passwd中的前兩個欄位呢?只需將剋服貪婪的正則當作整體重覆一次即可。

sed -r 's/^([^:]*):([^:]*):.*/hello \1 \2/' /etc/passwd

取第三個欄位?

sed -r 's/^([^:]*:){2}([^:]*):.*/hello \2/' /etc/passwd

取第三和第五個欄位?沒辦法,只能將第四個欄位顯式標註出來。

sed -r 's/^([^:]*:){2}([^:]*):([^:]*):([^:]*):/hello \2 \4/' /etc/passwd

取第三道第5欄位?更簡單,重覆3次就可以了。

sed -r 's/^([^:]*:){2}(([^:]*:){3}).*/hello \2/' /etc/passwd

但這樣的結果中,第3到第5欄位中必然會包含":"分隔符,想要去除它?洗洗睡吧!sed本就不擅長處理欄位,剋服貪婪匹配本就讓表達式變得很複雜不易讀,而且效率還不高。用它處理欄位,絕對是吃撐了。

5.sed命令"a"和"N"的糾葛

sed的"a"命令作用是將提供的文本數據隊列化在記憶體中,然後在模式空間內容輸出時追加在輸出流的尾部一併輸出。

例如,在匹配行"ccc"後插入一行數據"matched successful"。

echo -e "aaa\nbbb\nccc\nddd" | sed '/ccc/a matched successful'
aaa
bbb
ccc
matched successful
ddd

咋一使用"a"命令,很順利,沒毛病。但是結合"N"試試看?

echo -e "aaa\nbbb\nccc\nddd" | sed '/ccc/{a\
matched successful
;N}'

aaa
bbb
matched successful
ccc
ddd

不是追加在尾部嗎,怎麼跑匹配行的前面去了?即使"N"讀取了下一行,也應該是追加在"ddd"的下一行吧?想要真正弄明白這個問題,對sed模式空間的輸出機制必須瞭如指掌,可以參考sed修煉系列(一):花拳繡腿之入門篇。此處簡單描述下"N"命令的輸出機制。

無論是sed自動讀取下一行,還是"n"或"N"命令讀取下一行,只要有讀取動作,在其前面必然會輸出模式空間的內容。當"N"讀取下一行時,首先它會判斷是否還有下一行可供讀取,如果有,則先鎖住模式空間,然後自動輸出並清空模式空間,再解鎖模式空間並向其尾部追加一個換行符"\n",最後讀取下一行追加到換行符尾部。由於模式空間被鎖住,使得自動輸出時輸出流是空流,也同樣無法清空模式空間。註意,它不是禁止輸出,雖然輸出空流的結果和禁止輸出是一樣的,但輸出空流它有輸出動作,有輸出流,會寫入標準輸出,而禁止輸出則沒有輸出動作。如果沒有下一行可供讀取,則自動輸出模式空間、清空模式空間並退出sed程式。過程大致如下所描述:

if [ "$line" -ne "$last_line_num" ];then
    lock pattern_space;
    auto_print;
    remove_pattern_space;
    unlock pattern_space;
    append "\n" to pattern_space;
    read next_line to pattern_space;
else
    auto_print;
    remove_pattern_space;
    exit;
fi

回到"a"命令和"N"命令結合的問題上。之所以"a"命令的隊列化文本會插入在匹配行的前面,問題就出在輸出空流上。"N"在準備讀取下一行時,它有輸出動作,即使輸出結果為空。而"a"命令是時刻等待sed輸出流的,只要一有輸出流,立馬就會追上去追加在輸出流的屁股後面。因此,"matched successful"會追加在空流的尾部,追加之後"N"才會讀入下一行,最後輸出模式空間中的內容"ccc\nddd",也就得到前面"有悖期待"的結果。

 

sed系列文章:

sed修煉系列(一):花拳繡腿之入門篇
sed修煉系列(二):武功心法(info sed翻譯+註解)
sed修煉系列(三):sed高級應用之實現視窗滑動技術
sed修煉系列(四):sed中的疑難雜症

 

回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7499309.html

註:若您覺得這篇文章還不錯請點擊下右下角的推薦,有了您的支持才能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • DML(data manipulation language): 它們是SELECT、UPDATE、INSERT、DELETE,就象它的名字一樣,這4條命令是用來對資料庫里的數據進行操作的語言DDL(data definition language): DDL比DML要多,主要的命令有CREATE、 ...
  • Mysql only_full_group_by以及其他關於sql_mode原因報錯詳細解決方案 網上太多相關資料,但是抄襲嚴重,有的講的也是之言片語的,根本不連貫(可能知道的人確實不想多說) 我總共花了3個多小時,反覆測試,總結一下 Mysql only_full_group_by以及其他關於sq ...
  • 前言 tempdb暴增,造成磁碟空間不足,甚至影響業務運行。 正文 如圖,tempdb log文件從7.40開始突然暴漲,因為 tempdb 0 M到 40G tempdb 所在磁碟是C 盤 C盤的可用空間正好也為40G 在下午16.22左右的時候tempdb 文件暴漲已經影響到業務使用.臨時解決是 ...
  • 1.環境: 上位機:ubuntu16.04 Linux jello 4.4.0-89-generic #112-Ubuntu SMP Mon Jul 31 19:38:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 下位機: /home/jello # unam ...
  • 1.環境: ubuntu16.04 Linux jello 4.4.0-89-generic #112-Ubuntu SMP Mon Jul 31 19:38:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 2.背景: 最近在linux下拷貝文件時由於文件比較大 ...
  • 1.環境: /home/jello # uname -aLinux 3.10.0 #2 SMP Mon Mar 6 17:52:09 CST 2017 armv7l GNU/Linux 2.使用tcpsvd啟動ftp服務 tcpsvd -vE 0.0.0.0 21 ftpd ftpdir & (&表 ...
  • 看操作系統精髓與設計原理(Operating Systems Internals and Design Principles),附錄提到一個教學用的系統Nachos。試著找源碼,編譯一下。使用ubuntu16LTS,總編譯不過。 swtch.s彙編不過去,因為pushl無法編譯啊(因為本機是64bi ...
  • sed系列文章: sed修煉系列(一):花拳繡腿之入門篇sed修煉系列(二):武功心法(info sed翻譯+註解)sed修煉系列(三):sed高級應用之實現視窗滑動技術sed修煉系列(四):sed中的疑難雜症 說明: 第一篇是入門篇,但卻是最重要的一篇。雖然內容不算多,但在裡面講了絕大多數sed的 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...