一、元字元
詳情請見匹配規則。
二、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
命令
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
命令
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
子命令 i
和 a
用法基本一樣,只不過 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
- 將
i
和I
均替換為--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 Aho
,Peter Weinberger
和Brian Kernighan
的姓氏首字元。
2. awk
工作原理
awk
的命令格式如下:
awk 'BEGIN{ commands } pattern{ commands } END{ commands }'
基於上述命令格式,awk
的工作流程可分為三個部分:
- 讀輸入檔案之前執行的程式碼段(由
BEGIN
關鍵字標識);- 主迴圈執行輸入檔案的程式碼段;
- 讀輸入檔案之後的程式碼段(由
END
關鍵字標識)。
下面的流程圖詳細描述出了awk
的工作流程:
- 通過關鍵字
BEGIN
執行BEGIN
塊的內容,即BEGIN
後花括號{}
的內容;- 完成
BEGIN
塊的執行,開始執行BODY
塊;- 讀入由
\n
換行符分割的記錄,一條記錄即為一行;- 將記錄按指定的域分隔符劃分為域(相當於資料庫的欄位取值),其中
$0
表示所有域(即一行內容),$1
表示第一個域,$n
表示第n
個域;- 依次執行各
BODY
塊,pattern
部分匹配該行內容成功後,才會執行awk
命令中commands
部分的內容;- 迴圈讀取並執行各行直到檔案結束,完成
BODY
塊執行;- 開始
END
塊執行,END
塊可以輸出最終結果。
開始塊BEGIN
開始塊的語法格式如下:
BEGIN {awk-commands}
開始塊就是在程式啟動的時候執行的程式碼部分,並且它在整個過程中只執行一次。一般情況下,我們可以在開始塊中初始化一些變數。
BEGIN
是awk
的關鍵字,因此它必須是大寫的。
注意:開始塊部分是可選的,你的程式可以沒有開始塊部分。
主體塊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
大括號裡邊的就是awk
的command
,只能被單引號包含,其中,$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
記錄數NR
和FNR
如下,列印出讀取檔案的行數,因為是按行讀取,在應用場景中,行數可以等同於行號,用來輸出對應行的行號:
[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
裡面字母間是以:
來分割的,所以:
- 第一種寫法,由於沒有指定
FS
,awk
預設是以空格來分割,每一行所有內容都當作$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
內建支援一系列函式,其中length
計算字串長度,toupper
函式轉換字串為大寫。
[root@iZbp15brp59m56cywazt3yZ ~]# awk 'BEGIN { FS = ":" } { if (length($1) == 3) print $1, "\t", toupper($1) }' awk_sep_demo.txt
bin BIN
adm ADM
條件語句與迴圈
條件
利用NR
和FNR
這兩個內建變數,使用其條件語句,有一個有趣的應用,即比較兩個檔案awk_demo.txt
和awk_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
的時候NR
和FNR
都是從1開始計數,這時NR == FNR
將該行賦值給陣列; - 當
NR != FNR
此時表示已讀取到第二個檔案,將陣列中的內容和當前行$0
進行比較,如果不相同,則輸出行號和該行資訊。
下面的例子利用條件判斷,根據每一條記錄所屬的使用者,分別將各條記錄輸出至檔案root.txt
、bin.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
的陣列可以理解為字典或Map
,key
可以是數值和字串,這種資料型別在平時很常用。
[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
工作機制,我們再來看一個綜合的示例,假設有一個學生成績單:
[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
階段,我們先列印了表尾的格式,並列印總成績,以及計算了平均值。