Shell 編程之文本處理

前言

在日常工作學習中,不免經常要對文本文件(例如日誌文件)進行處理工作,包括但不限於分割、查找、替換、刪除等操作,Shell 中有沒有相應的命令供我們使用呢?那麼本篇文章,我們就一起來學習下吧!

cut

grep 命令可以查找文件中符合條件的行,cut 命令則可以根據分隔符,提取行中的列,默認分隔符爲 TAB

# cut [選項]  文件名

選項

-f 列號: 提取第幾列
-d 分隔符: 按照指定分隔符分割列


# cut默認分隔符是TAB

測試一下這個命令就容易理解了。我們首先新建一個文件,然後使用 TAB 分隔,然後使用 cut 命令提取列。

示例一

[root@VM-0-5-centos ~]# vim student.txt
ID      NAME    Gender  MARK
1       zs      M       98
2       ls      F       99

# 1. 提取第二列
[root@VM-0-5-centos ~]# cut -f 2 student.txt
NAME
zs
ls

# 2. 提取第二列和第四列
[root@VM-0-5-centos ~]# cut -f 2,4 student.txt
NAME MARK
zs 98
ls 99

# 3. 測試使用分隔符提取列

# 3.1 這是我們需要處理的文本
[root@VM-0-5-centos ~]# cat /etc/passwd | grep /bin/bash | grep -v root
lifelmy:x:1000:1000::/home/lifelmy:/bin/bash
user1:x:1001:1001::/home/user1:/bin/bash
user3:x:1002:1003::/home/user3:/bin/bash

# 3.2 提取第一列
[root@VM-0-5-centos ~]# cat /etc/passwd | grep /bin/bash | grep -v root | cut -f 1 -d :
lifelmy
user1
user3

從上面的例子我們可以看出,cut 命令可以提取行中的列,但是 cut 命令有一個缺點,就是分隔符的長度必須一致。如果列與列之間,有的是一個空格符,有的是兩個空格符的話,cut 命令就不能正確劃分列了。

示例二

[root@VM-0-5-centos ~]# df  -h
文件系統        容量  已用  可用 已用% 掛載點
devtmpfs        909M     0  909M    0% /dev
tmpfs           919M   24K  919M    1% /dev/shm
tmpfs           919M  540K  919M    1% /run
tmpfs           919M     0  919M    0% /sys/fs/cgroup
/dev/vda1        50G  5.6G   42G   12% /
tmpfs           184M     0  184M    0% /run/user/0

# 使用 空格 作爲分隔符,查出第一列
[root@VM-0-5-centos ~]# df  -h | cut -f 1 -d " "
文件系統
devtmpfs
tmpfs
tmpfs
tmpfs
/dev/vda1
tmpfs

# 但是查第二列的時候,得到的都是空格,不是我們想要的"容量"那一列
[root@VM-0-5-centos ~]# df  -h | cut -f 2 -d " "

awk

從上面的示例二可以看出,cut 還是有些缺點的,那麼這個問題有沒有其他命令可以解決呢,這就輪到我們的 awk 命令出場了。在詳細介紹 awk 命令前,我們先來看一下標準輸出命令。

在 awk 命令的輸出中支持 printprintf 命令

printf

printf "輸出類型 輸出格式" 輸出內容

[輸出類型]:

%ns: 輸出字符串,n表示輸出幾個字符
%ni: 輸出整數,n表示輸出幾個數字
%m.nf: 輸出浮點數,m、n分別表示輸出的位數以及其中的小數位數. %8.2f表示共輸出8位數,其中2位是小數,6位整數



[輸出格式]:

\a: 輸出警告聲音
\b: 輸出退格鍵
\f: 清除屏幕
\n: 換行
\r: 回車
\t: 水平輸出製表符
\v: 垂直輸出製表符

示例

# %-5s 格式爲左對齊且寬度爲5的字符串代替('-'表示左對齊),不使用則默認右對齊。
# %-4.2f 格式爲左對齊寬度爲4,保留兩位小數。

[root@VM-0-5-centos ~]# printf "%-5s %-10s %-4s\n" NO Name Mark
NO    Name       Mark
[root@VM-0-5-centos ~]# printf "%-5s %-10s %-4.2f\n" 01 Tom 90.3456
01    Tom        90.35
[root@VM-0-5-centos ~]# printf "%-5s %-10s %-4.2f\n" 02 Jack 89.2345
02    Jack       89.23
[root@VM-0-5-centos ~]# printf "%-5s %-10s %-4.2f\n" 03 Jeff 98.4323
03    Jeff       98.43


[root@VM-0-5-centos ~]# printf '%d %d %d\n' 12 34 56
12 34 56

awk 是一種編程語言,用於在 linux/unix 下對文本和數據進行處理。數據可以來自標準輸入 (stdin)、一個或多個文件,或其它命令的輸出。它支持用戶自定義函數和動態正則表達式等先進功能,是 linux/unix 下的一個強大編程工具。它在命令行中使用,但更多是作爲腳本來使用。awk 有很多內建的功能,比如數組、函數等,這是它和 C 語言的相同之處,靈活性是 awk 最大的優勢。

awk 'BEGIN{動作} 條件1{動作1} 條件2{動作2} END{動作}' file


條件(Pattern):
     一般使用關係表達式作爲條件
     x>10
     x<=10
 
動作(Action):
   格式化輸出
   流程控制語句

一個 awk 腳本由三部分組成:BEGIN 語句塊、能夠使用條件匹配的通用語句塊、END 語句塊 3 部分組成,這三個部分是可選的。任意一個部分都可以不出現在腳本中。

BEGIN 條件,會在讀取文件第一行之前執行相應的動作;END 條件,讀取完文件後執行。

測試

# 1. 在處理第一行文件時先執行BEGIN對應的動作,然後無條件對每一行使用默認的空格分隔後取出第5列,最後執行 END 對應的動作
[root@VM-0-5-centos ~]# df -h | awk 'BEGIN{printf "this is begin \n"} {printf $5 "\n"} END{printf "this is end \n"}'
this is begin
已用%
0%
1%
1%
0%
12%
0%
this is end

# 2. 在處理第一行文件前賦值 i=0,然後每處理一行就將i加一,最後輸出i的值
[root@VM-0-5-centos ~]# df -h | awk 'BEGIN{ i=0 } { i++ } END{ print i }'
7

# 3. 將1、2兩個例子結合起來
[root@VM-0-5-centos ~]# df -h | awk 'BEGIN{i=0 ;printf "start \n"} {printf $5 "\t"} {i++;  printf i "\n"}  END{printf "end\n"}'
start
已用% 1
0% 2
1% 3
1% 4
0% 5
12% 6
0% 7
end

由於 awk 命令的默認分隔符是空格 (沒有數量限制),因此對於分隔符不是空格的文件來說,BEGIN 的主要作用就是在處理文件前設置分隔符,其中FS 用來設置分隔符。

# 要處理的數據
[root@VM-0-5-centos ~]# cat /etc/passwd | grep syslog
syslog:x:996:994::/home/syslog:/bin/false

# 使用 BEGIN 設置分隔符爲":",然後輸出第三列數據
[root@VM-0-5-centos ~]# cat /etc/passwd | grep syslog | awk 'BEGIN {FS=":"} {printf $3 "\n"}'
996

# 使用關係運算符條件
[root@VM-0-5-centos ~]# cat /etc/passwd  | awk 'BEGIN {FS=":"} $3>995{printf $3 "\n"}'
999
998
997
996
1000
1001
1002

下面給出一個具體問題,思考下你會怎麼處理。上面我們已經使用過了 df -h 命令,該命令是用於查看 Linux 的磁盤佔用情況,我們想要設置個定時任務,每天去檢查某個磁盤的佔用情況,如果磁盤佔用比例到了 80%,就發送郵件報警。這裏我們只處理第一步,就是判斷磁盤佔用情況是否超出了設置的閾值。

腳本文件

#!/bin/bash

read -p 'please input rate: ' r
rate=$(df -h | grep 'vda1'| awk '{printf $5}' | cut -d '%' -f 1)
if [ $rate -gt $r ]
        then
                echo 'alarm'
fi

測試

[root@VM-0-5-centos ~]# chmod 755 test.sh

[root@VM-0-5-centos ~]# ./test.sh
please input rate: 20
[root@VM-0-5-centos ~]# ./test.sh
please input rate: 10
alarm

sed

sed 是一種幾乎包括所有 unix 平臺(包括 Linux)的輕量級流編輯器,它是文本處理中非常重要的工具,能夠完美的配合正則表達式使用,功能不同凡響。

處理時,把當前處理的行存儲在臨時緩衝區中,稱爲 “模式空間”(pattern space),接着用 sed 命令處理緩衝區中的內容,處理完成後,把緩衝區的內容送往屏幕。接着處理下一行,這樣不斷重複,直到文件末尾。文件內容並沒有改變,除非你使用重定向存儲輸出。sed 主要用來自動編輯一個或多個文件;簡化對文件的反覆操作;編寫轉換程序等。

# sed [選項] '動作' 文件名

選項:

-n  一般sed命令會把所有數據都輸出到屏幕,如果加入此選擇,則只會把經過sed命令處理的行輸出到屏幕
-e 允許對輸入數據應用多條sed命令編輯
-i  用sed的修改結果直接修改讀取數據的文件,而不是由屏幕輸出

動作:

a:  追加,在當前行後添加一行或多行。添加多行時,除最後一行外,每行末尾需要用'\' 代表數據未完結
c:  行替換,用'c' 後面的字符串替換原數據行,替換多行時,除最後一行外,每行末尾用'\' 表示數據未完結
i:  插入,在當前行前插入一行或多行。插入多行時,除最後一行外,每行末尾用'\' 表示數據未完結
d:  刪除,刪除指定的行
p:   打印,輸出指定的行
s:   字符串替換,用一個字符串替換另一個字符串。格式爲 "第幾行s/舊字串/新字串/g",使用 '/g'表示替換該行中的所有匹配  (與vim類似)

測試

# 要處理的文件
[root@VM-0-5-centos ~]# cat student.txt
ID NAME Gender MARK
1 zs M 98
2 ls F 99

# 打印輸出第2行,默認不僅會輸出指定的行,還會輸出原有文件中的所有行
[root@VM-0-5-centos ~]# sed '2p' student.txt
ID NAME Gender MARK
1 zs M 98
1 zs M 98
2 ls F 99

# -n,只輸出sed處理的數據
[root@VM-0-5-centos ~]# sed -n '2p' student.txt
1 zs M 98

# 刪除第2行的數據,但不修改原文件 (在緩衝區修改的)
[root@VM-0-5-centos ~]# sed '2d' student.txt
ID NAME Gender MARK
2 ls F 99
[root@VM-0-5-centos ~]# cat student.txt
ID NAME Gender MARK
1 zs M 98
2 ls F 99

# 在第3行後追加一條數據,不修改原數據 (在緩衝區修改的)
[root@VM-0-5-centos ~]# sed '3a hello world' student.txt
ID NAME Gender MARK
1 zs M 98
2 ls F 99
hello world

# 在第3行前插入一條數據,不修改原數據 (在緩衝區修改的)
[root@VM-0-5-centos ~]# sed '3i hello world' student.txt
ID NAME Gender MARK
1 zs M 98
hello world
2 ls F 99

# 替換第3行的數據,不修改原數據 (在緩衝區修改的)
[root@VM-0-5-centos ~]# sed '3c hello world' student.txt
ID NAME Gender MARK
1 zs M 98
hello world

# 替換字符串字段,不修改原數據 (在緩衝區修改的)
[root@VM-0-5-centos ~]# sed  '2s/98/100/g' student.txt
ID NAME Gender MARK
1 zs M 100
2 ls F 99

# 字符串替換,直接修改文件
[root@VM-0-5-centos ~]# sed -i '2s/98/100/g' student.txt
[root@VM-0-5-centos ~]# cat student.txt
ID NAME Gender MARK
1 zs M 100
2 ls F 99

# 替換多處
[root@VM-0-5-centos ~]# sed -i '2s/M/F/; 2s/zs/ww/' student.txt
[root@VM-0-5-centos ~]# cat student.txt
ID NAME Gender MARK
1 ww F 100
2 ls F 99

總結

本篇文章一共學習了三個命令:

PS:本文只是簡單的介紹了命令的常用部分,如果想要更加深入瞭解,可以去這裏 https://wangchujiang.com/linux-command/ 學習。

更多

個人博客: https://lifelmy.github.io/

微信公衆號:漫漫 Coding 路

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/g7CnIAtlAMimn9V_J2lFng