【Linux進階】使用grep、find、sed以及awk進行文字操作

solitude123發表於2021-06-22

一、元字元

詳情請見匹配規則

二、grep命令

下面通過實戰演示的方式介紹grep命令的常見用法,為此首先我們需要一個示例文字檔案,這裡使用CentOS作業系統/etc目錄下的passwd檔案:

[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description:    CentOS Linux release 8.2.2004 (Core) 
Release:        8.2.2004
Codename:       Core
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
systemd-coredump:x:999:997:systemd Core Dumper:/:/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/sbin/nologin
tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin
polkitd:x:998:996:User for polkitd:/:/sbin/nologin
libstoragemgmt:x:997:995:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
unbound:x:996:993:Unbound DNS resolver:/etc/unbound:/sbin/nologin
setroubleshoot:x:995:991::/var/lib/setroubleshoot:/sbin/nologin
cockpit-ws:x:994:990:User for cockpit web service:/nonexisting:/sbin/nologin
cockpit-wsinstance:x:993:989:User for cockpit-ws instances:/nonexisting:/sbin/nologin
sssd:x:992:988:User for sssd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
chrony:x:991:987::/var/lib/chrony:/sbin/nologin
rngd:x:990:986:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
nscd:x:28:28:NSCD Daemon:/:/sbin/nologin

1. 過濾出包含某字串的行

[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep "Kernel" /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin

# 忽略大小寫
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i "kernel" /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin

# 同時輸出行號
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -n "kernel" /etc/passwd
13:nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin

2. 過濾出以某字串開頭(結尾)的行

# 過濾出以sshd開頭的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep "^sshd" /etc/passwd
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

# 過濾出以shutdown結尾的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep "shutdown$" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

3. 過濾出包含某字串及其相鄰的行

# 將包含Kernel的行以及其下邊的一行過濾出來
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -A1  "kernel" /etc/passwd
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin

# 將包含Kernel的行以及其上邊的一行過濾出來
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -B1  "kernel" /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin

# 將包含Kernel的行以及其上邊和下邊的一行過濾出來
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -C1  "kernel" /etc/passwd
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin

4. 過濾出不包含某關鍵字的行

# 過濾出不包含nologin的行,並輸出其行號
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -v -n "nologin" /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
6:sync:x:5:0:sync:/sbin:/bin/sync
7:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8:halt:x:7:0:halt:/sbin:/sbin/halt

5. 過濾出包含多個字串中任意一個的行

# 過濾出包含shutdown或kernel的行
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -e "shutdown" -e  "kernel" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin

# 上述命令等價於下列命令
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -i -E "shutdown|kernel" /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin

6. 檢視目錄中包含某字串的所有檔案

# 遞迴查詢/etc/目錄下包含"nobody"在內的所有檔案
[root@iZbp1gwkhxi6nj3ztmah8uZ ~]# grep -r -n "nobody" /etc/
/etc/ssh/sshd_config:60:#AuthorizedKeysCommandUser nobody
/etc/aliases:29:nobody:         root
/etc/aliases:65:nfsnobody:      root
/etc/group-:24:nobody:x:65534:
/etc/gshadow-:24:nobody:::
/etc/passwd-:13:nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
/etc/shadow-:13:nobody:*:18358:0:99999:7:::
/etc/group:24:nobody:x:65534:
/etc/gshadow:24:nobody:::
/etc/passwd:13:nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
/etc/shadow:13:nobody:*:18358:0:99999:7:::
/etc/idmapd.conf:43:#Nobody-User = nobody
/etc/idmapd.conf:44:#Nobody-Group = nobody
/etc/pinforc:93:SAFE-USER=nobody
/etc/pinforc:94:SAFE-GROUP=nobody

三、find命令

參考Linux 檔案搜尋神器 find 實戰詳解,建議收藏!

1. 按檔名查詢

  • 查詢當前目錄下所有 c 語言檔案:
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# pwd
/usr/local/aegis/PythonLoader/lib/python2.7
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# find ./ -name "*.c"
./config/config.c
./distutils/tests/xxmodule.c
[root@iZbp1gjysfmcbcojeshiw7Z python2.7]# find ./ -name *.c
./config/config.c
./distutils/tests/xxmodule.c
  • 在當前目錄下,查詢大寫字母開頭的 txt 檔案:
[root@iZbp1gjysfmcbcojeshiw7Z lib2to3]# pwd
/usr/lib64/python3.6/lib2to3
[root@iZbp1gjysfmcbcojeshiw7Z lib2to3]# find ./ -name "[A-Z]*.txt"
./Grammar.txt
./PatternGrammar.txt
[root@iZbp1gjysfmcbcojeshiw7Z lib2to3]# find ./ -name "[A-Z]*.txt" -print
./Grammar.txt
./PatternGrammar.txt

其中 -print 表示行為(即 action ),預設指定; find 命令另一個常用的行為是 -prune ,表示不搜尋某一個目錄。

  • 在當前目錄下,查詢不是以 fix 開頭的 .py 檔案:
[root@iZbp14vmgrtj1265z7za9nZ fixers]# pwd
/usr/local/aegis/PythonLoader/lib/python2.7/lib2to3/tests/data/fixers
[root@iZbp14vmgrtj1265z7za9nZ fixers]# 
[root@iZbp14vmgrtj1265z7za9nZ fixers]# find ./ -name "*.py" -print
./parrot_example.py
./myfixes/__init__.py
./myfixes/fix_first.py
./myfixes/fix_preorder.py
./myfixes/fix_explicit.py
./myfixes/fix_last.py
./myfixes/fix_parrot.py
./no_fixer_cls.py
./bad_order.py
[root@iZbp14vmgrtj1265z7za9nZ fixers]# find ./ -name "fix*" -prune -o -name "*.py" -print
./parrot_example.py
./myfixes/__init__.py
./no_fixer_cls.py
./bad_order.py
  • 在當前目錄下,查詢不在 myfixes 目錄下的 .py 檔案:
[root@iZbp14vmgrtj1265z7za9nZ fixers]# find ./ -path "./myfixes" -prune -o -name "*.py" -print
./parrot_example.py
./no_fixer_cls.py
./bad_order.py

需要注意的是,當希望按照檔名稱搜尋時忽略大小寫,則應該使用選項 -iname 而不是 -name

2. 按檔案型別查詢

  • 在當前目錄下,查詢軟連線檔案,且指定最大遞迴深度為1:
[root@iZbp14vmgrtj1265z7za9nZ /]# pwd
/
[root@iZbp14vmgrtj1265z7za9nZ /]# find ./ -maxdepth 1 -type l -print
./bin
./sbin
./lib
./lib64
  • 在當前目錄下,查詢 log 結尾的普通檔案,f 表示普通檔案型別:
[root@iZbp14vmgrtj1265z7za9nZ /]# find ./ -type f -a -name "*.log"

3. 按檔案大小查詢

  • 查詢大小超過 64k 的檔案:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -size +64k -print
./moduli

4. 按檔案時間查詢

  • 查詢當前目錄下:
    • 修改時間在48小時以上的普通檔案;
    • 修改時間在72小時以上的普通檔案;
    • 修改時間在24小時以內的普通檔案;
    • 修改時間在24小時以上,48小時以內的普通檔案。
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime +1 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime +2 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime -1 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime -2 -type f -print
[root@iZbp14vmgrtj1265z7za9nZ ~]# find . -mtime 1 -type f -print

實際上,對於選項 -atime-ctime 也有類似的語法。

  • 查詢比 ssh_host_rsa_key 新或舊的檔案
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -newer "ssh_host_rsa_key" -type f -print
./sshd_config
./ssh_host_dsa_key
./ssh_host_dsa_key.pub
./ssh_host_ecdsa_key
./ssh_host_ecdsa_key.pub
./ssh_host_ed25519_key
./ssh_host_ed25519_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ ! -newer "ssh_host_rsa_key" -type f -print
./moduli
./ssh_config
./ssh_config.d/05-redhat.conf
./ssh_host_rsa_key
./ssh_host_rsa_key.pub

5. 按檔案許可權查詢

[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# ll
total 608
-rw-r--r--. 1 root root 577388 Feb  5  2020 moduli
-rw-r--r--. 1 root root   1716 Feb  5  2020 ssh_config
drwxr-xr-x. 2 root root     28 Nov 20  2020 ssh_config.d
-rw-------  1 root root   4296 Jun  1 14:27 sshd_config
-rw-------  1 root root   1401 Jun  1 14:26 ssh_host_dsa_key
-rw-r--r--  1 root root    618 Jun  1 14:26 ssh_host_dsa_key.pub
-rw-------  1 root root    525 Jun  1 14:26 ssh_host_ecdsa_key
-rw-r--r--  1 root root    190 Jun  1 14:26 ssh_host_ecdsa_key.pub
-rw-------  1 root root    419 Jun  1 14:26 ssh_host_ed25519_key
-rw-r--r--  1 root root    110 Jun  1 14:26 ssh_host_ed25519_key.pub
-rw-------  1 root root   2622 Jun  1 14:26 ssh_host_rsa_key
-rw-r--r--  1 root root    582 Jun  1 14:26 ssh_host_rsa_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type d -a -perm 755
.
./ssh_config.d
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type f -perm 644
./moduli
./ssh_config
./ssh_config.d/05-redhat.conf
./ssh_host_rsa_key.pub
./ssh_host_dsa_key.pub
./ssh_host_ecdsa_key.pub
./ssh_host_ed25519_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type f -perm 600
./sshd_config
./ssh_host_rsa_key
./ssh_host_dsa_key
./ssh_host_ecdsa_key
./ssh_host_ed25519_key
  • 查詢當前目錄下所有使用者都有執行許可權的檔案:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -perm -111
./
./ssh_config.d
  • 查詢當前目錄下至少一個使用者有寫許可權的檔案:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# ll | sed '1d' | nl
     1  -rw-r--r--. 1 root root 577388 Feb  5  2020 moduli
     2  -rw-r--r--. 1 root root   1716 Feb  5  2020 ssh_config
     3  drwxr-xr-x. 2 root root     28 Nov 20  2020 ssh_config.d
     4  -rw-------  1 root root   4296 Jun  1 14:27 sshd_config
     5  -rw-------  1 root root   1401 Jun  1 14:26 ssh_host_dsa_key
     6  -rw-r--r--  1 root root    618 Jun  1 14:26 ssh_host_dsa_key.pub
     7  -rw-------  1 root root    525 Jun  1 14:26 ssh_host_ecdsa_key
     8  -rw-r--r--  1 root root    190 Jun  1 14:26 ssh_host_ecdsa_key.pub
     9  -rw-------  1 root root    419 Jun  1 14:26 ssh_host_ed25519_key
    10  -rw-r--r--  1 root root    110 Jun  1 14:26 ssh_host_ed25519_key.pub
    11  -rw-------  1 root root   2622 Jun  1 14:26 ssh_host_rsa_key
    12  -rw-r--r--  1 root root    582 Jun  1 14:26 ssh_host_rsa_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -maxdepth 1 -perm /222 | nl
     1  ./
     2  ./moduli
     3  ./ssh_config
     4  ./ssh_config.d
     5  ./sshd_config
     6  ./ssh_host_rsa_key
     7  ./ssh_host_rsa_key.pub
     8  ./ssh_host_dsa_key
     9  ./ssh_host_dsa_key.pub
    10  ./ssh_host_ecdsa_key
    11  ./ssh_host_ecdsa_key.pub
    12  ./ssh_host_ed25519_key
    13  ./ssh_host_ed25519_key.pub

6. 按組合條件查詢

  • 查詢當前目錄下,所屬使用者為 root 的目錄:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# pwd
/etc/ssh
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . -type d -a -user root -print
.
./ssh_config.d
  • 查詢當前目錄下的非普通檔案:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -not -type f
./
./ssh_config.d
[root@iZbp14vmgrtj1265z7za9nZ ssh]# 
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ ! -type f
./
./ssh_config.d
  • 查詢當前目錄下的非空檔案:
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find . ! -empty

7. 查詢出檔案後做相應處理

通過 find 命令查詢出某個檔案之後,我們可以繼續使用 -exec-ok ,對其進行進一步的處理,如:

[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -name "*.pub" -exec ls -alh {} \;
-rw-r--r-- 1 root root 582 Jun  1 14:26 ./ssh_host_rsa_key.pub
-rw-r--r-- 1 root root 618 Jun  1 14:26 ./ssh_host_dsa_key.pub
-rw-r--r-- 1 root root 190 Jun  1 14:26 ./ssh_host_ecdsa_key.pub
-rw-r--r-- 1 root root 110 Jun  1 14:26 ./ssh_host_ed25519_key.pub
[root@iZbp14vmgrtj1265z7za9nZ ssh]# find ./ -name "*.pub" -ok ls -alh {} \;
< ls ... ./ssh_host_rsa_key.pub > ? n
< ls ... ./ssh_host_dsa_key.pub > ? n
< ls ... ./ssh_host_ecdsa_key.pub > ? y
-rw-r--r-- 1 root root 190 Jun  1 14:26 ./ssh_host_ecdsa_key.pub
< ls ... ./ssh_host_ed25519_key.pub > ? y
-rw-r--r-- 1 root root 110 Jun  1 14:26 ./ssh_host_ed25519_key.pub

由上述可知: -ok-exec 功能一樣,只是前者操作時會提示使用者確認,僅此而已。當然,在生產環境上,還是推薦使用 -ok

在這裡說明一下{}\;

  • {}其實它就是一個佔位符,在 find 命令的執行過程中會不斷地替換成當前找到的檔案,相當於ls -l 找到的檔案
  • \;-exec 的命令結束標記,因為規定 -exec 後面的命令必須以 ; 結束,但 ; 在 shell 中有特殊含義,必須要轉義,所以寫成 \;

四、sed命令

參考乾貨!上古神器 sed 教程詳解,小白也能看的懂

1. sed簡介

  • sed 全名叫 stream editor,即流編輯器,其使用方式與互動式文字編輯器截然不同的。 在使用互動式文字編輯器如 vim 時,使用者使用鍵盤通過互動的方式插入、刪除、替換文字;
  • sed 這樣的流編輯器使用提前編寫好的一系列命令來編輯文字,這些命令在編輯器處理文字之前就需要寫好
  • 因為這樣的使用方式, sed 尤其適用於某些情況下的文字編輯,如:希望通過自動化的方式批量修改大量的待編輯文字。

2. 工作流程

針對 sed 這樣的流編輯器,其工作流程大致如下:

  • 從輸入中一次讀入一行資料;
  • 將該行資料依次和預先寫好的命令進行匹配;
  • 當命令匹配成功時,對流中的資料進行相應修改;
  • 將修改後的資料輸出至STDOUT

3. 基本語法

4. 案例實戰

首先,為了便於後面演示,通過下列命令建立一個測試文字 sed_demo.txt

[root@iZbp15spmmi74px0lk8l6nZ ~]# head -n5 /etc/passwd > sed_demo.txt 
[root@iZbp15spmmi74px0lk8l6nZ ~]# cat -n sed_demo.txt 
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

定址

預設情況下,sed 會對文字的每一行都執行讀取、匹配、操作、輸出這些步驟,但很多時候我們只想對部分行執行這些步驟,而定位期望處理的目標行就叫做定址,根據方式不同,又可分為數字定址正則定址

數字定址

數字定址顧名思義就是通過數字的方式確定文字要處理的行:

  • 僅將第1行中的所有 root 替換為 ROOT
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed '1s/root/ROOT/g' sed_demo.txt | nl
     1  ROOT:x:0:0:ROOT:/ROOT:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 僅將第2-4行中的所有 sbin 替換為 SBIN
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed '2,4s/sbin/SBIN/g' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/SBIN/nologin
     3  daemon:x:2:2:daemon:/SBIN:/SBIN/nologin
     4  adm:x:3:4:adm:/var/adm:/SBIN/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 僅將從第2行開始,往下數3行,即2-5行中的所有 sbin 替換為 SBIN
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed '2,+3s/sbin/SBIN/g' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/SBIN/nologin
     3  daemon:x:2:2:daemon:/SBIN:/SBIN/nologin
     4  adm:x:3:4:adm:/var/adm:/SBIN/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/SBIN/nologin
  • 將除第3行以外所有行中的 sbin 替換為 SBIN
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed '3!s/sbin/SBIN/g' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/SBIN/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/SBIN/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/SBIN/nologin

正則定址

  • 僅將最後一行中的所有 sbin 替換為 SBIN
[root@iZbp15spmmi74px0lk8l6nZ ~]# sed '$s/sbin/SBIN/g' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/SBIN/nologin
  • 將匹配到以 daemon 開頭的行到以 adm 開頭的行及其之間的所有行進行刪除:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '/^daemon/,/^adm/d' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 刪除文件中的所有空行:
[root@iZbp142l91zbbe0hqz89bgZ ~]# cat -n sed_demo.txt 
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3
     4
     5  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     6
     7  adm:x:3:4:adm:/var/adm:/sbin/nologin
     8  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '/^$/d' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

數字定址和正則定址混用

  • 匹配從第1行到以 adm 開頭的行,並將匹配的行進行刪除:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '1,/^adm/d' sed_demo.txt | nl
     1  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

基本子命令

追加行子命令 a

  • 在所有行下方追加 /etc/passwd
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 'a /etc/passwd' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
/etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
/etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
/etc/passwd
adm:x:3:4:adm:/var/adm:/sbin/nologin
/etc/passwd
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
/etc/passwd
  • 僅在第1,2行之後追加/etc/passwd
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '1,2a /etc/passwd' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
/etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
/etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 僅將第1行及其向下兩行後追加 /etc/passwd
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '1,+2a /etc/passwd' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
/etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
/etc/passwd
daemon:x:2:2:daemon:/sbin:/sbin/nologin
/etc/passwd
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 僅在最後一行後追加 /etc/passwd
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '$a /etc/passwd' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
/etc/passwd

插入行子命令 i

子命令 ia 用法基本一樣,只不過 i 是在指定行上方插入指定的內容行:

[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '$i /etc/passwd' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
/etc/passwd
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

替換子命令 s

上面的案例中,通過定址指定的待操作行之後的 s 即表示替換,這也是 sed 最常用的一種功能:

其基本語法為:

sed [address] s /pat/rep/flags

  • 僅將每行匹配到的第一個 bin 替換為 BIN
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/bin/BIN/' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/BIN/bash
     2  BIN:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sBIN:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sBIN/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sBIN/nologin
  • 將每行匹配到的所有 bin 替換為 BIN (其中 g 代表 global 即全域性之意):
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/bin/BIN/g' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/BIN/bash
     2  BIN:x:1:1:BIN:/BIN:/sBIN/nologin
     3  daemon:x:2:2:daemon:/sBIN:/sBIN/nologin
     4  adm:x:3:4:adm:/var/adm:/sBIN/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sBIN/nologin
  • 每行第2次匹配的 bin 替換為 BIN
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/bin/BIN/2' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:BIN:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sBIN/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 每行第2次出現到該行結束所有的 bin 替換為 BIN
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/bin/BIN/2g' sed_demo.txt | nl
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:BIN:/BIN:/sBIN/nologin
     3  daemon:x:2:2:daemon:/sbin:/sBIN/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 在每一行的行首插入符號兩個製表符:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/^/\t\t/g' sed_demo.txt 
                root:x:0:0:root:/root:/bin/bash
                bin:x:1:1:bin:/bin:/sbin/nologin
                daemon:x:2:2:daemon:/sbin:/sbin/nologin
                adm:x:3:4:adm:/var/adm:/sbin/nologin
                lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 在每一行的行尾插入-------
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/$/-------/g' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash-------
bin:x:1:1:bin:/bin:/sbin/nologin-------
daemon:x:2:2:daemon:/sbin:/sbin/nologin-------
adm:x:3:4:adm:/var/adm:/sbin/nologin-------
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin-------
  • 將2-3行的 sbin 替換為 SBIN ,同時將第3行至最後一行的 nologin 替換為 NOLOGIN
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '2,3s/sbin/SBIN/g; 3,$s/nologin/NOLOGIN/g' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/SBIN/nologin
daemon:x:2:2:daemon:/SBIN:/SBIN/NOLOGIN
adm:x:3:4:adm:/var/adm:/sbin/NOLOGIN
lp:x:4:7:lp:/var/spool/lpd:/sbin/NOLOGIN

替換行子命令 c

  • 將1-3行替換為 /etc/passwd
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '1,3c /etc/passwd' sed_demo.txt 
/etc/passwd
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

刪除行子命令 d

  • 刪除1-3行:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '1,3d' sed_demo.txt 
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

設定行號子命令 =

  • 僅為第1-2行設定行號:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '1,2=' sed_demo.txt 
1
root:x:0:0:root:/root:/bin/bash
2
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

讀取當前行的同時讀取其下一行子命令 N

其實就是將當前行的下一行內容也讀進快取區,一起做匹配和修改,需要注意的是:當前行的 \n 仍然保留。

[root@iZbp142l91zbbe0hqz89bgZ ~]# sed '=' sed_demo.txt | sed 'N;s/\n/\t/'
1       root:x:0:0:root:/root:/bin/bash
2       bin:x:1:1:bin:/bin:/sbin/nologin
3       daemon:x:2:2:daemon:/sbin:/sbin/nologin
4       adm:x:3:4:adm:/var/adm:/sbin/nologin
5       lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

為便於理解上述命令,我們先看一下單獨執行 sed '=' sed_demo.txt 的效果:

[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed '=' sed_demo.txt 
1
root:x:0:0:root:/root:/bin/bash
2
bin:x:1:1:bin:/bin:/sbin/nologin
3
daemon:x:2:2:daemon:/sbin:/sbin/nologin
4
adm:x:3:4:adm:/var/adm:/sbin/nologin
5
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

即行號會插入在在每一行的前一行,而後 sed 'N;s/\n/\t/' 是:

  • 先將行號和其下一行一起讀取;
  • 然後將換行符(\n)替換為製表符(\t)。

忽略大小寫子命令 i

  • iI 均替換為 --I--
# 僅替換每一行的第一個i或I
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed 's/nologin/NOLOGIN/' sed_demo.txt | nl | sed 's/i/--I--/i'
     1  root:x:0:0:root:/root:/b--I--n/bash
     2  b--I--n:x:1:1:bin:/bin:/sbin/NOLOGIN
     3  daemon:x:2:2:daemon:/sb--I--n:/sbin/NOLOGIN
     4  adm:x:3:4:adm:/var/adm:/sb--I--n/NOLOGIN
     5  lp:x:4:7:lp:/var/spool/lpd:/sb--I--n/NOLOGIN
# 替換
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed 's/nologin/NOLOGIN/' sed_demo.txt | nl | sed 's/i/--I--/gi'
     1  root:x:0:0:root:/root:/b--I--n/bash
     2  b--I--n:x:1:1:b--I--n:/b--I--n:/sb--I--n/NOLOG--I--N
     3  daemon:x:2:2:daemon:/sb--I--n:/sb--I--n/NOLOG--I--N
     4  adm:x:3:4:adm:/var/adm:/sb--I--n/NOLOG--I--N
     5  lp:x:4:7:lp:/var/spool/lpd:/sb--I--n/NOLOG--I--N

基本選項

多個命令連線 -e

上述命令 sed '2,3s/sbin/SBIN/g; 3,$s/nologin/NOLOGIN/g' sed_demo.txt 等價於下列命令:

[root@iZbp142l91zbbe0hqz89bgZ ~]# sed -e '2,3s/sbin/SBIN/g' -e '3,$s/nologin/NOLOGIN/g' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/SBIN/nologin
daemon:x:2:2:daemon:/SBIN:/SBIN/NOLOGIN
adm:x:3:4:adm:/var/adm:/sbin/NOLOGIN
lp:x:4:7:lp:/var/spool/lpd:/sbin/NOLOGIN

關閉列印模式 -n

下面命令僅列印出了發生替換的行 (關閉每行都列印的同時,指定 p 來列印僅發生替換的行):

[root@iZbp142l91zbbe0hqz89bgZ ~]# sed -n 's/nologin/NOLOGIN/p' sed_demo.txt | nl
     1  bin:x:1:1:bin:/bin:/sbin/NOLOGIN
     2  daemon:x:2:2:daemon:/sbin:/sbin/NOLOGIN
     3  adm:x:3:4:adm:/var/adm:/sbin/NOLOGIN
     4  lp:x:4:7:lp:/var/spool/lpd:/sbin/NOLOGIN

修改原始檔 -i

需要注意的是:至此, sed 修改匹配到的內容後,預設不會將修改儲存到原檔案,而是直接輸出修改後模式空間(可理解為快取)的內容到STDOUT,如果要修改原檔案需要指定 -i 選項。

支援擴充套件正規表示式 -r-e

  • 刪除檔案每行的第二個字元:
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed -r 's/(.)(.)(.*)$/\1\3/' sed_demo.txt 
rot:x:0:0:root:/root:/bin/bash
bn:x:1:1:bin:/bin:/sbin/nologin
demon:x:2:2:daemon:/sbin:/sbin/nologin
am:x:3:4:adm:/var/adm:/sbin/nologin
l:x:4:7:lp:/var/spool/lpd:/sbin/nologin
  • 交換每行的第一個字元和第二個字元:
[root@iZbp1gjysfmcbcojeshiw7Z ~]# sed -r 's/(.)(.)(.*)$/\2\1\3/' sed_demo.txt 
orot:x:0:0:root:/root:/bin/bash
ibn:x:1:1:bin:/bin:/sbin/nologin
ademon:x:2:2:daemon:/sbin:/sbin/nologin
dam:x:3:4:adm:/var/adm:/sbin/nologin
pl:x:4:7:lp:/var/spool/lpd:/sbin/nologin

特殊變數

  • 使用變數 & 可以代表匹配出的結果:
[root@iZbp142l91zbbe0hqz89bgZ ~]# sed 's/nologin/"&"/g' sed_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/"nologin"
daemon:x:2:2:daemon:/sbin:/sbin/"nologin"
adm:x:3:4:adm:/var/adm:/sbin/"nologin"
lp:x:4:7:lp:/var/spool/lpd:/sbin/"nologin"

使用正則

  • 去除下面文字中HTML標籤(其中 [^>]* 表示 '>' 的字元出現0次或多次)
[root@iZbp142l91zbbe0hqz89bgZ ~]# echo '<b>This</b> is what <span style="x">I</span> meant' | sed 's/<[^>]*>//g'
This is what I meant

下面的正則中:

  • \([^,]\) 中的 \ 表示指定括號為逃逸字元,^含義為
  • \1\2 分別表示使用分組。
[root@iZbp142l91zbbe0hqz89bgZ ~]# echo "hello,123,world" | sed 's/\([^,]\),.*,\(.*\)/\1=\2/'
hello=world
sed -i 's/upjas.bind.address=19.20.11.134/upjas.bind.address='`hostname -i`'/g' upjas_setenv.properties

五、awk命令

本部分主要參考下列文章,僅記錄以供個人學習參考:

1. awk簡介

上面介紹的sed可以實現非互動式的字串替換,grep能夠實現有效的過濾功能。與兩者相比,awk是一款強大的文字分析工具,尤其擅長對日誌、csv檔案等格式化資料進行分析並生成報告。

該命令之所以叫awk是因為其取了三位創始人Alfred AhoPeter WeinbergerBrian Kernighan的姓氏首字元。

2. awk工作原理

awk的命令格式如下:

awk 'BEGIN{ commands } pattern{ commands } END{ commands }'

基於上述命令格式,awk的工作流程可分為三個部分:

  • 讀輸入檔案之前執行的程式碼段(由BEGIN關鍵字標識);
  • 主迴圈執行輸入檔案的程式碼段;
  • 讀輸入檔案之後的程式碼段(由END關鍵字標識)。

下面的流程圖詳細描述出了awk的工作流程:

在這裡插入圖片描述

  1. 通過關鍵字BEGIN執行BEGIN塊的內容,即BEGIN後花括號{}的內容;
  2. 完成BEGIN塊的執行,開始執行BODY塊;
  3. 讀入由\n換行符分割的記錄,一條記錄即為一行;
  4. 將記錄按指定的域分隔符劃分為域(相當於資料庫的欄位取值),其中$0表示所有域(即一行內容),$1表示第一個域,$n表示第n個域;
  5. 依次執行各BODY塊,pattern部分匹配該行內容成功後,才會執行awk命令中commands部分的內容;
  6. 迴圈讀取並執行各行直到檔案結束,完成BODY塊執行;
  7. 開始END塊執行,END塊可以輸出最終結果。

開始塊BEGIN

開始塊的語法格式如下:

BEGIN {awk-commands}

開始塊就是在程式啟動的時候執行的程式碼部分,並且它在整個過程中只執行一次。一般情況下,我們可以在開始塊中初始化一些變數。

BEGINawk的關鍵字,因此它必須是大寫的。

注意:開始塊部分是可選的,你的程式可以沒有開始塊部分。

主體塊BODY

主體部分的語法格式如下:

/pattern/ {awk-commands}

對於每一個輸入的行都會執行一次主體部分的命令。

預設情況下,對於輸入的每一行,awk都會執行命令。但是,我們可以將其限定在指定的模式中。

注意:在主體塊部分沒有關鍵字存在。

結束塊END

結束塊的語法格式如下:

END {awk-commands}

結束塊是在程式結束時執行的程式碼。END也是awk的關鍵字,它也必須大寫。

注意:與開始塊相似,結束塊也是可選的。

3. awk實戰演示

為了演示方便,我們使用下列命令,先建立一個用於演示用的TXT文件:

[root@iZbp1ewxwj89u3zpajnxz4Z sys]# ls -l > /root/awk_demo.txt
[root@iZbp1ewxwj89u3zpajnxz4Z sys]# cd /root
[root@iZbp1ewxwj89u3zpajnxz4Z ~]# cat awk_demo.txt 
drwxr-xr-x   2 root root 0 May 21  2021 block
drwxr-xr-x  35 root root 0 May 21  2021 bus
drwxr-xr-x  54 root root 0 May 21  2021 class
drwxr-xr-x   4 root root 0 May 21  2021 dev
drwxr-xr-x  15 root root 0 May 21  2021 devices
drwxr-xr-x   6 root root 0 May 21  2021 firmware
drwxr-xr-x   6 root root 0 May 21  2021 fs
drwxr-xr-x   2 root root 0 May 21 16:37 hypervisor
drwxr-xr-x  14 root root 0 May 21  2021 kernel
drwxr-xr-x 114 root root 0 May 21  2021 module
drwxr-xr-x   2 root root 0 May 21 16:37 power

輸出

輸出指定列

# 輸出文件awk_demo.txt的第1,4,8列
[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk '{print $1,$4,$8}' awk_demo.txt 
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 16:37
drwxr-xr-x root 2021
drwxr-xr-x root 2021
drwxr-xr-x root 16:37

大括號裡邊的就是awkcommand只能被單引號包含,其中,$1..$N表示第幾列,$0表示整個的行內容。

格式化輸出

awk還支援型別C語言printf函式的格式化輸出:

[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk '{printf "%-20s %-8s %-10s %-8s\n",$1,$2,$3,$4}' awk_demo.txt 
drwxr-xr-x           2        root       root    
drwxr-xr-x           35       root       root    
drwxr-xr-x           54       root       root    
drwxr-xr-x           4        root       root    
drwxr-xr-x           15       root       root    
drwxr-xr-x           6        root       root    
drwxr-xr-x           6        root       root    
drwxr-xr-x           2        root       root    
drwxr-xr-x           14       root       root    
drwxr-xr-x           114      root       root    
drwxr-xr-x           2        root       root

其中,和C語言型別,%s表示字串佔位符,-20表示列寬度為20且左對齊。

輸出過濾的行

僅輸出第3列為root且第8列為16:37的行:

[root@iZbp1ewxwj89u3zpajnxz4Z ~]# awk '$3 == "root" && $8 == "16:37"  {print $0}' awk_demo.txt 
drwxr-xr-x   2 root root 0 May 21 16:37 hypervisor
drwxr-xr-x   2 root root 0 May 21 16:37 power

awk支援各種比較運算子號!=><>=<=,其中$0表示整行的所有內容。

使用內建變數

欄位數NF

如下,awk在讀取檔案時,按行讀取,每一行的欄位數(列數),賦值給內建變數NF,列印出來的就是每行的欄位總數:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print "欄位數:" NF}' awk_demo.txt 
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9
欄位數:9

如果只需要最後一列的資料,由於每一行的列數可能不一,最後一列無法指定固定的列數,此時就可以使用NF來表示列數,'{print $NF}'表示列印出等於總列數的那一列的資料。

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print $NF}' awk_demo.txt 
block
bus
class
dev
devices
firmware
fs
hypervisor
kernel
module
power

記錄數NRFNR

如下,列印出讀取檔案的行數,因為是按行讀取,在應用場景中,行數可以等同於行號,用來輸出對應行的行號:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print "行號為:" NR}' awk_demo.txt 
行號為:1
行號為:2
行號為:3
行號為:4
行號為:5
行號為:6
行號為:7
行號為:8
行號為:9
行號為:10
行號為:11

NR還可以用作判斷輸出,如下簡單例子:

# 過濾第8列為"10:52"且行號大於9的記錄,列印時僅列印行號、每條記錄第一個欄位、每條記錄最後一個欄位
[root@iZbp15brp59m56cywazt3yZ ~]# awk '$8 == "10:52" {if(NR>9)print NR,$1,$NF}' awk_demo.txt 
11 drwxr-xr-x power

FNR也是讀取檔案的行數,但是和NR不同的是,當讀取的檔案有兩個或兩個以上時,NR讀取完一個檔案,行數繼續增加,而FNR重新從1開始記錄:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print "NR:" NR "\t\t" "FNR:" FNR}' awk_demo.txt awk_demo.txt 
NR:1            FNR:1
NR:2            FNR:2
NR:3            FNR:3
NR:4            FNR:4
NR:5            FNR:5
NR:6            FNR:6
NR:7            FNR:7
NR:8            FNR:8
NR:9            FNR:9
NR:10           FNR:10
NR:11           FNR:11
NR:12           FNR:1
NR:13           FNR:2
NR:14           FNR:3
NR:15           FNR:4
NR:16           FNR:5
NR:17           FNR:6
NR:18           FNR:7
NR:19           FNR:8
NR:20           FNR:9
NR:21           FNR:10
NR:22           FNR:11

輸入欄位分隔符FS

awk中預設分隔一行各個欄位的符號為空格,而實際的文字資料並不總是以空格為分隔符,我們可以通過FS變數指定分隔符,為了後續演示方便,下面先建立一個以:分隔的文件:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{ if (NR < 6) print $0 }' /etc/passwd > /root/awk_sep_demo.txt
[root@iZbp15brp59m56cywazt3yZ ~]# cat awk_sep_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

如下所示,因為awk_sep_demo.txt裡面字母間是以:來分割的,所以:

  • 第一種寫法,由於沒有指定FSawk預設是以空格來分割,每一行所有內容都當作$1來顯示;
  • 第二個指定FS:分割,所以能按照我們期望的方式來列印。
[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print $1}' awk_sep_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@iZbp15brp59m56cywazt3yZ ~]# awk 'BEGIN { FS = ":" } { print $1 }' awk_sep_demo.txt 
root
bin
daemon
adm
lp

實際上,通過選項-F也可以實現類似的功能:

[root@iZbp15brp59m56cywazt3yZ ~]# awk -F ':' '{ print $1 }' awk_sep_demo.txt 
root
bin
daemon
adm
lp

再推廣一下,如果想要一次性指定多個分隔符,如:,;等,可以使用類似-F '[;:,]'的格式。

輸出欄位分隔符OFS

輸出欄位分割符,預設為空格,實際需求可能要求輸出是以若干個製表符分割,可以使用OFS進行格式化輸出:

[root@iZbp15brp59m56cywazt3yZ ~]# awk 'BEGIN { FS = ":"; OFS = "\t\t\t" } { print $1, $2, $3 }' awk_sep_demo.txt 
root                    x                       0
bin                     x                       1
daemon                  x                       2
adm                     x                       3
lp                      x                       4

輸入行分隔符RS

輸入行分隔符RS,用於判斷輸入部分的行的起始位置,預設是換行符,下面將其改為:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{if(NR == 1) print $0}' awk_sep_demo.txt | awk 'BEGIN { RS = ":" } { print $0 }'
root
x
0
0
root
/root
/bin/bash

你可能注意到/bin/bash之後有一空行,原因在於:通過awk '{if(NR == 1) print $0}' awk_sep_demo.txt獲得的是檔案中的第一行,該行最後一個看不見的換行符'\n'

輸出行分割符ORS

輸出行分割符,預設的是換行符,它的機制和OFS機制一樣,對輸出格式有要求時,可以進行格式化輸出:

[root@iZbp15brp59m56cywazt3yZ ~]# awk 'BEGIN { ORS = "\t\t" } { print }' awk_sep_demo.txt 
root:x:0:0:root:/root:/bin/bash         bin:x:1:1:bin:/bin:/sbin/nologin                daemon:x:2:2:daemon:/sbin:/sbin/nologin         adm:x:3:4:adm:/var/adm:/sbin/nologin          lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin                

簡單資料統計

如下,下面的命令統計當前目錄下,所有檔案大小的總和:

[root@iZbp15brp59m56cywazt3yZ ~]# ls -l
total 20
-rw-r--r-- 1 root root 511 May 26 11:07 awk_demo.txt
-rw-r--r-- 1 root root 183 May 26 14:50 awk_sep_demo.txt
-rw-r--r-- 1 root root  33 May 26 16:37 bin.txt
-rw-r--r-- 1 root root 118 May 26 16:37 others.txt
-rw-r--r-- 1 root root  32 May 26 16:37 root.txt
[root@iZbp15brp59m56cywazt3yZ ~]# ls -l | awk '{ sum += $5 } END { print sum }'
877

第5列表示檔案大小,每讀取一行就會將該檔案大小計算到sum變數中,在最後END階段列印出sum,也就是所有檔案的大小總和。

4. awk進階

陣列

awk陣列

內建函式

awk內建函式

awk內建支援一系列函式,其中length計算字串長度,toupper函式轉換字串為大寫。

[root@iZbp15brp59m56cywazt3yZ ~]# awk 'BEGIN { FS = ":" } { if (length($1) == 3) print $1, "\t", toupper($1) }' awk_sep_demo.txt 
bin      BIN
adm      ADM

條件語句與迴圈

條件

awk條件語句與迴圈

利用NRFNR這兩個內建變數,使用其條件語句,有一個有趣的應用,即比較兩個檔案awk_demo.txtawk_sep_demo.txt是否一致,以awk_demo.txt作為參考,不一致的輸出行號和該行資訊:

[root@iZbp15brp59m56cywazt3yZ ~]# awk '{print "NR:" NR "\t\t" "FNR:" FNR}' awk_demo.txt awk_sep_demo.txt 
NR:1            FNR:1
NR:2            FNR:2
NR:3            FNR:3
NR:4            FNR:4
NR:5            FNR:5
NR:6            FNR:6
NR:7            FNR:7
NR:8            FNR:8
NR:9            FNR:9
NR:10           FNR:10
NR:11           FNR:11
NR:12           FNR:1
NR:13           FNR:2
NR:14           FNR:3
NR:15           FNR:4
NR:16           FNR:5
[root@iZbp15brp59m56cywazt3yZ ~]# awk '{ if (NR == FNR) { array[NR] = $0 } else { if (array[FNR] != $0) { print FNR, array[FNR] } } }' awk_demo.txt awk_sep_demo.txt 
1 drwxr-xr-x   2 root root 0 May 26  2021 block
2 drwxr-xr-x  35 root root 0 May 26  2021 bus
3 drwxr-xr-x  54 root root 0 May 26  2021 class
4 drwxr-xr-x   4 root root 0 May 26  2021 dev
5 drwxr-xr-x  15 root root 0 May 26  2021 devices

上述awk語句的含義為:

  • 當讀取第一個檔案awk_demo.txt的時候NRFNR都是從1開始計數,這時NR == FNR將該行賦值給陣列;
  • NR != FNR此時表示已讀取到第二個檔案,將陣列中的內容和當前行$0進行比較,如果不相同,則輸出行號和該行資訊。

下面的例子利用條件判斷,根據每一條記錄所屬的使用者,分別將各條記錄輸出至檔案root.txtbin.txt以及others.txt

[root@iZbp15brp59m56cywazt3yZ ~]# cat awk_sep_demo.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@iZbp15brp59m56cywazt3yZ ~]# awk 'BEGIN { FS = ":" } { if($1 == "root") print > "root.txt"; \
else if($1 == "bin") print > "bin.txt"; \
else print > "others.txt" }' awk_sep_demo.txt
[root@iZbp15brp59m56cywazt3yZ ~]# cat root.txt 
root:x:0:0:root:/root:/bin/bash
[root@iZbp15brp59m56cywazt3yZ ~]# cat bin.txt 
bin:x:1:1:bin:/bin:/sbin/nologin
[root@iZbp15brp59m56cywazt3yZ ~]# cat others.txt 
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

迴圈

下面的例子用到了陣列for迴圈,值得一提的是,awk的陣列可以理解為字典或Mapkey可以是數值和字串,這種資料型別在平時很常用。

[root@iZbp15brp59m56cywazt3yZ ~]# ps -aux | awk 'BEGIN { OFS = "\t\t\t" } { if(NR != 1) array[$1] += $6 } END { for(i in array) print i, array[i]}'
systemd+                        8876
chrony                  3864
polkitd                 21816
dbus                    5484
rngd                    6488
libstor+                        1860
root                    283532

需要注意的是,由於第一行為表頭,需要通過條件判斷if(NR != 1)將其排除。

使用者自定義函式

awk使用者自定義函式

awk指令碼編寫與執行

為了從整體上理解awk工作機制,我們再來看一個綜合的示例,假設有一個學生成績單:

[root@iZbp15brp59m56cywazt3yZ ~]# cat scores.txt 
Marry   2143    78      84      77
Jack    2321    66      78      45
Tom     2122    48      77      71
Mike    2537    87      97      95
Bob     2415    40      57      62

由於此示例程式稍顯複雜,在命令列上不易讀,另外呢,也想通過此案例介紹另外一種 awk的執行方式,我們的awk指令碼如下:

[root@iZbp15brp59m56cywazt3yZ ~]# cat cal_scores.awk 
#!/bin/awk -f
BEGIN {
        math = 0
        English = 0
        computer = 0
        printf "NAME    NO.     MATH    ENGLISH         COMPUTER        TOTAL\n"
        printf "-------------------------------------------------------------\n"
}
{
        math += $3
        English += $4
        computer += $5
        print $1, "\t", $2, "\t", $3, "\t", $4, "\t\t", $5, "\t\t", $3 + $4 + $5
}
END {
        printf "-------------------------------------------------------------\n"
        print "TOTAL:", "\t\t", math, "\t", English, "\t\t", computer
        print "AVERAGE:", "\t", math / NR, "\t", English / NR, "\t\t", computer / NR
}

執行awk結果如下:

[root@iZbp15brp59m56cywazt3yZ ~]# awk -f cal_scores.awk scores.txt 
NAME    NO.     MATH    ENGLISH         COMPUTER        TOTAL
-------------------------------------------------------------
Marry    2143    78      84              77              239
Jack     2321    66      78              45              189
Tom      2122    48      77              71              196
Mike     2537    87      97              95              279
Bob      2415    40      57              62              159
-------------------------------------------------------------
TOTAL:           319     393             350
AVERAGE:         63.8    78.6            70

我們可以將複雜的 awk 語句寫入指令碼檔案 cal_scores.awk,然後通過 -f 選項指定從指令碼檔案執行。

  • BEGIN 階段,我們初始化了相關變數,並列印了表頭的格式;
  • body 階段,我們讀取每一行資料,計算該學科和該同學的總成績;
  • END 階段,我們先列印了表尾的格式,並列印總成績,以及計算了平均值。

相關文章