Linux:管道命令與文本處理三劍客(grep、sed、awk)

来源:https://www.cnblogs.com/orion-orion/archive/2023/04/18/17328082.html
-Advertisement-
Play Games

眾所周知,bash命令執行的時候會輸出信息,但有時這些信息必須要經過幾次處理之後才能得到我們想要的格式,此時應該如何處置?這就牽涉到 管道命令(pipe) 了。管道命令使用的是|這個界定符號。每個管道後面接的第一個數據必定是命令,而且這個命令必須要能夠接受標準輸出的數據才行,這樣的命令才可為管道命令... ...


1 管道命令(pipe)介紹

眾所周知,bash命令執行的時候會輸出信息,但有時這些信息必須要經過幾次處理之後才能得到我們想要的格式,此時應該如何處置?這就牽涉到 管道命令(pipe) 了。管道命令使用的是|這個界定符號。另外,管道命令與連續執行命令是不一樣的,這點下麵我們會說明。

我們先來看一個管道命令的例子。假設我們需要看/etc目錄下有多少文件,那麼可以利用ls /etc來查看,不過由於文件數量太多,導致一口氣就將屏幕塞滿了,而不知道前面輸出的內容是啥:

root@orion-orion:~ ls -al /etc                                                                 root@qi
total 944
drwxr-xr-x 1 root root    4096 Feb 19 11:38 .
drwxr-xr-x 1 root root    4096 Nov 23  2021 ..
drwxr-xr-x 3 root root    4096 Jun  5  2021 .java
...
drwxr-xr-x 2 root root    4096 Jul 24  2018 xfce4

此時,我們可以使用less命令的協助:

root@orion-orion:~ ls -al /etc | less 
total 944
drwxr-xr-x 1 root root    4096 Feb 19 11:38 .
drwxr-xr-x 1 root root    4096 Nov 23  2021 ..
drwxr-xr-x 3 root root    4096 Jun  5  2021 .java
:

如此一來,使用ls命令輸出的內容就能夠被less讀取,並且利用less的功能,我們就能夠前後翻動相關的信息了。其中的關鍵就是這個管道命令|。管道命令|僅能處理前一個命令傳來的標準輸出信息,而對於標準錯誤信息並沒有直接處理能力。那麼整體的管道命令可以使用下圖表示:

在每個管道後面接的第一個數據必定是命令,而且這個命令必須要能夠接受標準輸出的數據才行,這樣的命令才可為管道命令。例如lessgrepsedawk等都是可以接受標準輸入的管道命令,而lscpmv就不是管道命令,因為它們並不會接受來自stdin的數據。總結一下,管道命令主要有兩個需要註意的地方:

  • 管道命令僅會處理標準輸出,對於標準錯誤會予以忽略
  • 管道命令必須要能夠接受來自前一個命令的數據成為標準輸入繼續處理才行(這也是其與連續執行命令之不同)。

如果我們強行讓標準錯誤為管道命令所用,那麼可以使用2>&1將標準錯誤2>重定向到標準輸出1>

接下來我們選取grepsedawk這三個用於文本處理的管道命令來進行介紹。這三個命令可謂是Linux下操作文本的三大利器,合稱Linux文本處理三劍客

2 行選取命令grep

grep命令可以一行一行地分析信息,若某行含有我們所需要的信息,則就將該行拿出來。簡單的語法如下:

grep [-acinv] [--color=auto] '查找字元' filename

它的選項與參數如下:

  • -a:將二進位文件以文本文件的方式查找數據。
  • -c:計算找到'查找字元'的次數。
  • -i:忽略大小寫的不同,所以大小寫視為相同。
  • -n:順便輸出行號。
  • -v:反向選擇,亦即顯示出沒有'查找字元'內容的那些行。

下麵展示幾個例子。

範例一:將last當中,有出現root的那一行就顯示出來。

root@orion-orion:~ last | grep 'root'
root     pts/2        10.249.252.8     Mon Apr  6 06:08 - 09:02  (02:54)
root     pts/1        10.249.252.8     Mon Apr  6 06:05 - 06:08  (00:03)
root     pts/1        10.249.252.8     Mon Apr  6 03:13 - 06:05  (02:51)
...
root     pts/1        :1               Tue Jul 24 06:44 - 06:45  (00:00)
root     pts/1        172.17.0.1       Tue Apr 10 14:23 - 14:23  (00:00)
root     pts/1        127.0.0.1        Tue Apr 10 08:57 - 08:57  (00:00)

這裡前3行是我們校內的區域網IP(以10.249打頭),172.17.0.1是Docker中預設網橋docker0的IP地址,127.0.0.1為本地迴環地址。
範例二:與範例一相反,只要沒有root的就取出。

root@orion-orion:~ last | grep -v 'root'
person   pts/1        127.0.0.1        Tue Apr 10 08:54 - 08:54  (00:00)

範例三:在last的輸出信息中,只要有root就取出,並且僅取第一欄:

root@orion-orion:~ last | grep "root" | awk '{print $1}' 
root
root
root
...

這裡用到了我們後面要講的awk命令,這一命令用於將一行分為多個欄位來處理,我們後面將會詳細介紹。
範例四:取出/etc/adduser.conf內含UID的那幾行,且將找到的關鍵字部分用特殊顏色顯示出來:

root@orion-orion:~ grep --color=auto "UID" /etc/adduser.conf                                                                  root@qi
# FIRST_SYSTEM_[GU]ID to LAST_SYSTEM_[GU]ID inclusive is the range for UIDs
# package, may assume that UIDs less than 100 are unallocated.
FIRST_SYSTEM_UID=100
LAST_SYSTEM_UID=999
# FIRST_[GU]ID to LAST_[GU]ID inclusive is the range of UIDs of dynamically
FIRST_UID=1000
LAST_UID=29999

可以看到找到的關鍵字部分用紅色顯示(當然這裡的代碼塊看不出來效果,需要在終端進行渲染)。註意,在我的Ubuntu 18.04系統中預設的grep已經主動使用--color=auto選項在alias中了,因此不用手動加--color=auto也會標紅(事實上,在我本地的Mac系統中也是如此)。

3 行操作命令sed

前面我們說過,grep命令可以解析一行文字,若該行含有某關鍵詞就會將其整行列出來。接下來我們要講的sed命令也是一個管道命令(可以分析標準輸入),它還可以對特定行進行新增、刪除、替換等sed的用法如下:

sed [-nefr] [操作]

它的選項與參數如下:

  • -n:使用安靜(silent)模式。在一般的sed用法中,所有來自stdin的數據一般都會被列出到屏幕上,但如果加上-n參數後,則只有經過sed選擇的那些行才會被列出來。
  • -e:使sed的操作結果由屏幕輸出,而改變原有文件(預設已選該參數, 與-i的直接修改文件相反)。
  • -f:從一個文件內讀取將要執行的sed操作,-f filename可以執行filename中寫好的sed操作。
  • -rsed的操作使用的是擴展型正則表達式的語法(預設是基礎正則表達式語法)。
  • -i:直接修改讀取的文件內容,而不是由屏幕輸出。

關於其中的[操作]部分,其格式如下:

[n1[,n2]]function

n1, n2:不一定會存在,一般代表選擇進行操作的行數,比如我的操作需要在10到20行之間進行,則寫為10, 20[操作名稱]

具體地,對行的操作函數function包括下麵這些東西:

  • a:新增,a的後面可以接字元,這些字元將被添加在n1/n2下一行
  • c:替換,c的後面可以接字元,這些字元可以替換n1n2之間的行;
  • d:刪除,因為是刪除,所以d後面通常不需要接任何東西;
  • i:插入,i的後面可以接字元,這些字元將被添加在n1/n2上一行
  • p:列印,亦即將某些選擇的行列印出來。通常p會與參數sed -n一起運行。
  • s:替換,可以直接進行替換的工作,通常這個s的操作可以搭配正則表達式。

下麵我們來舉幾個例子進行說明。

以行為單位的新增/刪除功能

範例一:查看/etc/passwd文件的內容並且在每一行前面加上行號,同時將2-5行刪除。

root@orion-orion:~ cat -n /etc/passwd | sed '2,5d'
    1  root:x:0:0:root:/root:/bin/zsh
    6  games:x:5:60:games:/usr/games:/usr/sbin/nologin
    7  man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
    8  lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
    ...

可以看到sed的操作為2,5d,也即刪除2~5行,所以顯示的數據就沒有2~5行。此外,請註意原本應該是要執行sed -e才對,不過這裡沒有-e也行,因為已經預設選了。同時也要註意sed後面接的操作務必以兩個單引號''括住。

我們將範例變一下,如果要刪除第2行,那麼可以使用cat -n /etc/passwd | sed '2d' ;如果是要刪除第3到最後一行,則是cat -n /etc/passwd | sed '3,$d',這裡美元符號$代表最後一行。

範例二: 承接上題,在第2行後(亦即是第3行)加上drink tea字樣。

root@orion-orion:~ cat -n /etc/passwd | sed '2a Drink tea?'
     1  root:x:0:0:root:/root:/bin/zsh
     2  daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
Drink tea?
     3  bin:x:2:2:bin:/bin:/usr/sbin/nologin
     ...

如果想要加在第2行前面,將新增操作改為插入操作,即cat -n /etc/passwd | sed '2i Drink tea?'就行了。

範例三:繼續承接上題,現在我們想要在第2行後面加上兩行字,例如Drink tea or...Drink beer?

root@orion-orion:~ cat -n /etc/passwd | sed '2a Drink tea or...\                                                 root@qi
\ Drink beer?'
     1  root:x:0:0:root:/root:/bin/zsh
     2  daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
Drink tea or...
Drink beer?
     3  bin:x:2:2:bin:/bin:/usr/sbin/nologin

這裡的重點在於我們可以不只增加一行,可以增加很多行,但每一行之間必須以反斜杠\來進行新行的增加。

以行為單位的替換與顯示功能

剛剛是介紹如何新增與刪除行。接下來我們看看如何進行整行的替換。

範例四:我想將2~5行的內容替換為No 2-5 number

root@orion-orion:~ cat -n /etc/passwd | sed '2,5c No 2-5 number`   
     1  root:x:0:0:root:/root:/bin/zsh
No 2-5 number
     6  games:x:5:60:games:/usr/games:/usr/sbin/nologin
     ...

除此之外,sed還有很有趣的功能,以前我們想要列出第11~25行,得用head -n 20tail -n 10之類的命令來處理,很麻煩。而sed則可以直接取出你想要的那幾行,這是通過行號來識別的。例如下麵這個範例:

範例五:僅列出/etc/passwd文件內的第5-7行。

root@orion-orion:~ cat -n /etc/passwd | sed -n '5,7p'
     5  sync:x:4:65534:sync:/bin:/bin/sync
     6  games:x:5:60:games:/usr/games:/usr/sbin/nologin
     7  man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

註意,這裡必須要加-n表示安靜模式。如果不加-n改為sed 5,7p,那麼第5-7行會重覆輸出:

root@orion-orion:~ cat -n /etc/passwd | sed '5,7p'
     1  root:x:0:0:root:/root:/bin/zsh
     2  daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
     3  bin:x:2:2:bin:/bin:/usr/sbin/nologin
     4  sys:x:3:3:sys:/dev:/usr/sbin/nologin
     5  sync:x:4:65534:sync:/bin:/bin/sync
     5  sync:x:4:65534:sync:/bin:/bin/sync
     6  games:x:5:60:games:/usr/games:/usr/sbin/nologin
     6  games:x:5:60:games:/usr/games:/usr/sbin/nologin
     7  man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
     7  man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
     8  lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
     ...

部分數據的查找並替換的功能

除了整行的處理模式之外,sed還可以對某行進行部分數據的查找和替換。基本上sed的查找與替換與vi相當的類似,它的格式如下所示:

sed 's/要被替換的字元/新的字元/g'

接下來我們來看一個取得IP數據的範例,我們將該任務拆解為多步,一段一段地處理。

步驟一:先觀察原始信息,利用/sbin/ifconfig查詢IP是什麼?

root@orion-orion:~ /sbin/ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:0c  
          inet addr:172.17.0.12  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:49498631 errors:0 dropped:0 overruns:0 frame:0
          TX packets:41131666 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:51467728818 (51.4 GB)  TX bytes:40995045195 (40.9 GB)

我們希望用關鍵詞識別出172.17.0.12,這是我所用學校伺服器的內網私有IP地址。

步驟二:利用關鍵字配合grep選取出inet所在的那關鍵的一行。

root@orion-orion:~ /sbin/ifconfig eth0 | grep 'inet '
          inet addr:172.17.0.12  Bcast:172.17.255.255  Mask:255.255.0.0

好了,現在只剩下一行了,但我們想只留下IP地址addr:172.17.0.12而將其它部分統統刪除。

步驟三:先將IP前面的部分予以刪除(用到正則表達式)。

root@orion-orion:~ /sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'
addr:172.17.0.12  Bcast:172.17.255.255  Mask:255.255.0.0

這裡正則表達式中的^表示待查找的字元串在行首;.表示一定有一個任意字元,*表示重覆前一個字元0到無窮多次,連在一起的.*就表示任意字元重覆任意次;//之間為空,也就表示刪除的意思。

步驟四:將IP後面的部分也予以刪除。

root@orion-orion:~ /sbin/ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' \
\ | sed 's/ *Bcast.*$//g' 
addr:172.17.0.12

這裡' *'表示空格重覆任意次;.*表示任意字元重覆任意次;$表示待查找的字元串在行尾。

我們再來繼續研究sed與正則表達式的配合練習。假設我們只要/etc/adduser.conf 文件中UID存在的那幾行數據,但是有#在內的註釋我們不要,而且空白行我們也不要,那麼應該如何處理?可以通過這幾個步驟來實踐看看:

步驟一:先用grep將關鍵字UID所在行取出來。

root@orion-orion:~ cat /etc/adduser.conf  | grep 'UID'
# FIRST_SYSTEM_[GU]ID to LAST_SYSTEM_[GU]ID inclusive is the range for UIDs
# package, may assume that UIDs less than 100 are unallocated.
FIRST_SYSTEM_UID=100
LAST_SYSTEM_UID=999
# FIRST_[GU]ID to LAST_[GU]ID inclusive is the range of UIDs of dynamically
FIRST_UID=1000
LAST_UID=29999

步驟二:刪除掉註釋之後的內容:

root@orion-orion:~ cat /etc/adduser.conf | grep 'UID' | sed 's/#.*$//g' 


FIRST_SYSTEM_UID=100
LAST_SYSTEM_UID=999

FIRST_UID=1000
LAST_UID=29999

這樣原本註釋的內容都變成了空白行,接下來我們刪除空白行:

root@orion-orion:~ cat /etc/adduser.conf | grep 'UID' | sed 's/#.*$//g' | sed '/^$/d'
FIRST_SYSTEM_UID=100
LAST_SYSTEM_UID=999
FIRST_UID=1000
LAST_UID=29999

註意這裡的^$表示行首^和行尾$之間沒有字元,也即空白行。

直接修改文件內容(危險操作)

sed的能耐可不止於我們上面所說的,它甚至可以直接修改文件的內容,而不必使用管道命令或數據流重定向。不過這個操作會修改到原始的文件,所以請你千萬不要隨便拿系統配置文件來測試。我們下麵使用regular_express.txt文件來測試(文件可以去鳥哥的官網下載:regular_express.txt)。

root@orion-orion:~ cat regular_express.txt                                                                    root@qi
"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
...
go! go! Let's go.
# I am VBird

範例六:利用sedregular_express.txt內每一行結尾若為.則換成!

root@orion-orion:~ sed -i 's/\.$/\!/g' regular_express.txt
root@orion-orion:~ cat regular_express.txt
"Open Source" is a good mechanism to develop programs!
apple is my favorite food!
Football game is not use feet only!
this dress doesn't fit me!
...
go! go! Let's go!
# I am VBird

上面的-i選項可以讓你的sed直接去修改後面所接的文件內容而不是由屏幕輸出。

範例七:利用sed直接在regular_express.txt最後一行加入# This is a test

root@orion-orion:~ sed -i '$a # This is a test' regular_express.txt
root@orion-orion:~ tail regular_express.txt
...
go! go! Let's go!
# I am VBird

# This is a test

由於$代表的是最後一行,而a的操作是新增,因此是該文件最後新增。

sed-i選項可以直接修改文件內容,因此這功能非常有幫助。比如如果你有一個100萬行的文件,你要在第100行加某些文字,此時使用vim可能會瘋掉,因為文件太大了。此時就可以利用sed來直接修改與替換,而不需要使用vim去修改了。

4 欄位操作命令awk

相較於sed常常對一整行進行操作,awk則傾向於將一行分為多個欄位來處理。因此,awk相當適合處理小型的文本數據,其運行模式通常是這樣的:

awk '{條件類型1{操作1} 條件類型2{操作2} ...}' filename

awk後接兩個單引號並加上大括弧{}來設置想要對數據進行的處理操作。awk可以處理後續接的文件,也可以讀取來自前一個命令的標準輸出。如前面所說,awk主要是將每一行分為多個欄位來處理,而預設的欄位分隔符為空格鍵[Tab]鍵。舉例來說,我們用last將登陸者的數據取出來(僅取出前3行):

root@orion-orion:~ last -n 3
root     pts/292      10.249.45.37     Wed Mar 29 06:55 - 09:14  (02:19)
root     pts/292      10.249.45.37     Tue Mar 28 13:17 - 16:14  (02:56)
root     pts/292      10.249.45.37     Tue Mar 28 12:35 - 13:17  (00:42)

wtmp begins Tue Apr 10 08:54:45 2018

若我想取出賬號與登陸者的IP,且賬號與IP之間以[Tab]隔開,則會變成這樣:

root@orion-orion:~ last -n 3 | awk '{print $1 "\t" $3}'                                                       root@qi
root    10.249.45.37
root    10.249.45.37
root    10.249.45.37

註意,awk的所有後續操作都是以單引號括住的,而awk的格式內容如果想要以print列印時,記得將非變數的文字部分使用雙引號括起來,因為單引號已經是awk命令的固定用法了。此外,因為這裡無論哪一行我們都要處理,因此就不需要有條件類型的限制。

另外,由上面的例子我們看到,在awk的括弧內,每一行的每個欄位都有變數名稱($1$2等)。在上面的例子中,root位於第1欄,故其變數名稱為$1;而10.249.45.37是第3欄,故它是$3,後面以此類推。還有個變數比較特殊,那就是$0,它表示一整行數據。由此可知,剛剛上面5行當中,整個awk的處理流程就是:

  1. 一次性讀入第1行整行的數據並存入$0,然後將其拆分為多個欄位並寫入$1$2$3等變數當中。
  2. 根據條件類型的限制,判斷是否需要進行後面的操作(在上面這個例子中沒有條件類型)。
  3. 完成所有操作與條件類型。
  4. 若還有後續行的數據,則重覆上面1~3的步驟,直到所有的數據都讀完為止。

經過這樣的步驟,我們看到了awk以行為一次處理的單位,而以欄位為最小的處理單位。好了,那麼如何快速地獲得我們的數據有幾行幾列呢?這就需要awk的內置變數的幫忙。

變數名稱 代表意義
NF 每一行(也即$0)所擁有的欄位總數
NR 目前awk所處理的是第幾行數據
FS 目前的分割字元,預設是空格鍵

我們繼續以上面last -n 3的例子來做說明,如果我想要:

  • 列出每一行的賬號(也就是$1);
  • 列出目前處理的行數(就是awk內的NR變數);
  • 並且說明該行有多少欄位(也就是awk內的NF欄位);

則可以這樣:

root@orion-orion:~ last -n 5 | awk '{print $1 "\t lines: " NR "\t columns: " NF}'
root     lines: 1        columns: 10
root     lines: 2        columns: 10
root     lines: 3        columns: 10
         lines: 4        columns: 0
wtmp     lines: 5        columns: 7

註意,在awk內的NRNF等變數要用大寫,且不需要有美元符號$

接下來我們來看一看所謂的“條件類型”。

awk 的邏輯運算字元
既然要用到“條件”的類別,那麼自然就需要一些邏輯運算,如下所示:

運算單元 代表意義
> 大於
< 小於
>= 大於或等於
<= 小於或等於
== 等於
!= 不等於

註意,邏輯運算即所謂的大於、小於等於等判斷式上面,習慣上用==而不是=來表示,=符號在awk操作這裡留給了變數賦值用。

我們來看下麵一個例子。比如在/etc/passwd中是以冒號:來作為欄位的分隔,該文件中第一欄位為賬號,第三欄位為UID。如下所示:

root@orion-orion:~ cat /etc/passwd | less                                                                     root@qi
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
dnsmasq:x:115:65534:dnsmasq,,,:/var/lib/misc:/bin/false

那假設我要查看第三欄小於10的數據,並且僅列出賬號與第三列,那麼可以這樣做:

root@orion-orion:~ cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t " $3}'
root:x:0:0:root:/root:/bin/zsh   
daemon   1
bin      2
...

誒,不過怎麼第一行沒有正確地顯示出來?這是因為我們在讀入第一行的時候,那些變數$1$2等等預設還是以空格鍵做為分割,所以雖然我們定義了FS=":",但卻僅能在第二行後才開始生效。那怎麼辦呢?我們可以預先設置awk的變數,利用BEGIN這個關鍵詞,這樣做:

root@orion-orion:~ cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'
root     0
daemon   1
bin      2
...

接下來我們來看如何用awk來完成計算功能。

假設我們有一個薪資數據表文件為pay.txt,內容如下:

root@orion-orion:~ cat pay.txt
Name    1st     2nd     3th
VBird   23000   24000   25000
DMTsai  21000   20000   23000
Bird2   43000   42000   41000

如何來計算每個人1st2nd3th的總額呢?而且我們還需要格式化輸出。我們可以這樣考慮:

  • 第一行只是表頭,所以第一行不進行求和而僅需要對錶頭進行列印(也即NR==1時處理)。
  • 第二行以後進行求和(NR>=2以後處理)。
root@orion-orion:~ cat pay.txt | \
awk 'NR == 1 {printf "%10s %10s %10s %10s %10s\n", $1, $2, $3, $4, "Total"} \
NR >= 2 {total = $2 + $3 + $4; \
printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
      Name        1st        2nd        3th      Total
     VBird      23000      24000      25000   72000.00
    DMTsai      21000      20000      23000   64000.00
     Bird2      43000      42000      41000  126000.00

上面的例子有幾個重要事項應該要先說明:

  • awk的命令間隔:所有awk的操作,亦即在{}里的操作,如果有需要多個命令輔助時,可利用分號;間隔。
  • 邏輯運算中,如果是“等於”的情況,請務必使用==
  • 格式化輸出時,在printf的格式設置中,務必加上\n,才能分行(這裡註意可以和Python的print函數和shell的echo函數做對比,此二者自帶換行);
  • 與bash shell中的變數不同,awk中的變數可以直接使用,不需要加上$符號。

另外,awk的操作內{}也是支持if( 條件 )的,比如上面的命令也可以寫為:

root@orion-orion:~ cat pay.txt | \
awk '{if (NR == 1) printf "%10s %10s %10s %10s %10s\n", $1, $2, $3, $4, "Total"} \
NR >= 2 {total = $2 + $3 + $4; \
printf "%10s %10d %10d %10d %10.2f\n", $1, $2, $3, $4, total}'
      Name        1st        2nd        3th      Total
     VBird      23000      24000      25000   72000.00
    DMTsai      21000      20000      23000   64000.00
     Bird2      43000      42000      41000  126000.00

第一種寫法相較於第二種寫法更好,因為比較有統一性。

除此之外,awk還可以幫我們進行迴圈計算,不過那屬於比較高級的單獨課程了,這裡就不再多加以介紹。

5 習題

情景模擬題一

通過grep配合子命令$(command)來從大量文件中查找含有星號*的文件與內容。

  1. 我們先來看如何在/etc下麵找出含有星號*的文件與內容。
root@orion-orion:~ grep '\*' /etc/* 2> /dev/null  
/etc/adduser.conf:#NAME_REGEX="^[a-z][-a-z0-9_]*\$"
/etc/bash.bashrc:#xterm*|rxvt*)
/etc/bash.bashrc:#*)
...

註意,這裡單引號''內的型號是正則表達式的字元,但由於我們要找的是星號,因此需要加上轉義符\;而/etc/*的那個*是bash通配符中的“萬用字元”,在這裡代表擁有任意多個字元的文件名。

不過在上述的這個例子中,我們僅能找到/etc下第一層子目錄的數據,無法找到次目錄的數據。如果想要連同完整的/etc此目錄數據,就得要這樣做:

root@orion-orion:~ grep '\*' $(find /etc -type f) 2> /dev/null
Binary file /etc/ld.so.cache matches
/etc/xdg/xfce4/xinitrc:  for i in ${XDG_CONFIG_HOME}/autostart/*.desktop; do
/etc/xdg/xfce4/xinitrc: x|xno*)
/etc/xdg/xfce4/xinitrc: *)
...

如果只想列出文件名而不想列出內容的話,可以加個-l參數:

root@orion-orion:~ grep -l '\*' $(find /etc -type f) 2> /dev/null
/etc/ld.so.cache
/etc/xdg/xfce4/xinitrc
/etc/xdg/Thunar/uca.xml
/etc/skel/.bashrc
...
  1. 又是文件數量會太多,比如如果我們要找的是全系統/的話:
root@orion-orion:~ grep '\*' $(find / -type f) 2> /dev/null  

蕪湖,一運行這個命令,由於要列印的東西太多,終端直接卡死。這下該如何是好呢?此時我們可以通過管道命令以及xargs來處理。比如,讓grep每次僅能處理10個文件名,我們可以:

a. 先用find去找出文件;
b. 用xargs將這些文件每次丟10個給grep來作為參數處理;
c. grep實際開始查找文件內容;

所以整個做法會變成這樣:

root@orion-orion:~ find / -type f 2> /dev/null | xargs -n 10 grep '\*'
Binary file /sbin/chcpu matches
Binary file /sbin/sulogin matches
Binary file /sbin/pivot_root matches
...

然而,從輸出的結果看,數據量實在非常龐大,如果我們只想知道文件名的話也可以給grep加上-l參數:

root@orion-orion:~ find / -type f 2> /dev/null | xargs -n 10 grep -l '\*'
/sbin/chcpu
/sbin/sulogin
/sbin/pivot_root
...

情景模擬題二

使用管道命令配合正則表達式建立新命令與新變數。我們想要建立一個名為myip的新命令,這個命令能夠將我系統的IP識別出來並顯示。而且我們想要有個新變數MYIP來記錄我們的IP。

處理的方式如下所示:

  1. 首先根據我們前面所講的ifconfigsedawk來取得我們的IP:
root@orion-orion:~ ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' | sed 's/ *Bcast.*$//g'
addr:172.17.0.12
  1. 接著,我們可以將此命令利用alias指定為myip,如下所示:
root@orion-orion:~ alias myip="ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' | \
\ sed 's/ *Bcast.*$//g'"
root@orion-orion:~ myip
addr:172.17.0.12
  1. 最終,我們可以通過變數設置來處理MYIP
root@orion-orion:~ MYIP=$(myip) 
~/orion-orion echo $MYIP
addr:172.17.0.12
  1. 如果每次登陸都要生效,可以將aliasMYIP設置的那兩行寫入你的~/.bashrc即可。

參考

  • [1] 鳥哥. 鳥哥的 Linux 私房菜: 基礎學習篇(第四版)[M]. 人民郵電出版社, 2018.
數學是符號的藝術,音樂是上界的語言。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 背景 在微服務項目中,大家都會去使用到分散式鎖,一般也是使用Redis去實現,使用RedisTemplate、Redisson、RedisLockRegistry都行,公司的項目中,使用的是Redisson,一般你會怎麼用?看看下麵的代碼,是不是就是你的寫法 String lockKey = "fo ...
  • 接下來本來就直接打算分享框架重構的具體環節,但重構的代碼其實並沒有完成太多,許多的實現細節在我心中還沒有形成一個定型。由於最近回歸崗位後,新的開發環境需要自己搭建,搭建的時間來說花了我整整一天的時間才勉強搞定。人們常說工欲善其事必先利其器,開發環境和工具是必不可少的,否則你會發現在接下來的過程中遇到 ...
  • 消息隊列中間件是分散式系統中重要的組件,主要解決應用耦合,非同步消息,流量削鋒等問題 實現高性能,高可用,可伸縮和最終一致性架構。最全面的Java面試網站 使用較多的消息隊列有 RocketMQ,RabbitMQ,Kafka,ZeroMQ,MetaMQ 以下介紹消息隊列在實際應用中常用的使用場景。 異 ...
  • 原創:扣釘日記(微信公眾號ID:codelogs),歡迎分享,非公眾號轉載保留此聲明。 在之前的OOM問題復盤中,我們添加了jmap腳本來自動dump記憶體現場,方便排查OOM問題。 但當我反覆模擬OOM場景測試時,發現jmap有時可以dump成功,有時會報錯,如下: 經過網上一頓搜索,發現兩種原因可 ...
  • demo軟體園每日更新資源,請看到最後就能獲取你想要的: 1.多語言BNB鏈上智能合約區塊鏈 別人發的我沒啥用,還有前面發的和這個好像不一樣 自己需要的下載玩,這個本來就沒有後臺,別下載了找我說不完整。看著還是挺不錯的。 這玩意好像還有人改盜u 頁面效果: 1.數據挖掘與預測分析 數據挖掘與預測分析 ...
  • 操作系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 一、安裝ilbc庫 從第三方庫里下載指定版本: git clone https://freeswitch.org/stash/scm/sd/libilbc.git 如果下載過慢,可從如下途徑獲取: 關註微信公眾號(聊 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. 使用方式 在 VS 中使用 VLD 的方法可以查看另外一篇博客:在 VS 2015 中使用 VLD。 2. 輸出報告 在 VS 中使用 VLD 時的輸出報告,與在 QT 中使用時是一致的 ...
  • .NET 實現JWT登錄認證 在ASP.NET Core應用程式中,使用JWT進行身份驗證和授權已成為一種流行的方式。JWT是一種安全的方式,用於在客戶端和伺服器之間傳輸用戶信息。 添加NuGet包 首先,我們需要添加一些NuGet包來支持JWT身份驗證。在您的ASP.NET Core項目中,打開S ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...