該系列是基于牛客Shell題庫,針對具體題目進行查漏補缺,學習相應的指令。
刷題連結:牛客題霸-Shell篇。
該系列文章都放到專欄下,專欄連結為:《專欄:Linux》。歡迎關注專欄~
本文知識預告:
- 首先學習了批量字元轉換、壓縮、删除的文本工具
指令相關的知識;tr
- 然後結合之前學習的指令給出六種題目的解決方案。
題目:SHELL6 去掉空行
寫一個 bash腳本以去掉一個文本檔案nowcoder.txt中的空行。假設nowcoder.txt 内容如下:
abc
567
aaa
bbb
ccc
相關指令學習
sed
:批量編輯文本檔案
sed
sed
指令來自于英文詞組“stream editor”的縮寫,其功能是用于利用文法/腳本對文本檔案進行批量的編輯操作。
sed
指令最初由貝爾實驗室開發,後被衆多Linux系統接納內建,能夠通過正規表達式對檔案進行批量編輯,讓需要重複的工作不再浪費時間。
sed
是非互動式的編輯器。它不會修改檔案,除非使用shell重定向來儲存結果。預設情況下,所有的輸出行都被列印到螢幕上。
sed
編輯器逐行處理檔案(或輸入),并将結果發送到螢幕。
sed
指令行格式為:
sed [-nefri] ‘command’ 輸入文本
常用選項:
-
:使用安靜(silent)模式。在一般-n
的用法中,所有來自STDIN的資料一般都會被列出到螢幕上。但如果加上sed
參數後,則隻有經過-n
特殊處理的那一行(或者動作)才會被列出來。sed
-
:直接在指令列模式上進行-e
的動作編輯;sed
-
:直接将-f
的動作寫在一個檔案内,sed
則可以執行-f filename
内的filename
動作;sed
-
:-r
的動作支援的是延伸型正規表示法的文法。(預設是基礎正規表示法文法)sed
-
:直接修改讀取的檔案内容,而不是由螢幕輸出。-i
常用指令:
-
:新增, a 的後面可以接字串,而這些字串會在新的一行出現(目前的下一行)~a
-
:取代, c 的後面可以接字串,這些字串可以取代c
之間的行!n1,n2
-
:删除,因為是删除啊,是以 d 後面通常不接任何咚咚;d
-
:插入, i 的後面可以接字串,而這些字串會在新的一行出現(目前的上一行);i
-
:列印,亦即将某個選擇的資料印出。通常 p 會與參數p
一起運作~sed -n
-
:取代,可以直接進行取代的工作哩!通常這個s
的動作可以搭配正規表示法!例如s
1,20s/old/new/g!
-
:是行内進行全局替換g
常用參數:
參數 | 功能 |
---|---|
| 以指定的腳本來處理輸入的文本檔案 |
| 以指定的腳本檔案來處理輸入的文本檔案 |
| 顯示幫助 |
| 僅顯示 處理後的結果 |
| 顯示版本資訊 |
參考執行個體
- 查找指定檔案中帶有某個關鍵詞的行:
[email protected]:~/shell$ sed -n '/main/p' nowcoder.txt
int main()
- 替換指定檔案中某個關鍵詞成大寫形式:
[email protected]:~/shell$ sed 's/int/INT/g' nowcoder.txt
#include <iostream>
using namespace std;
INT main()
{
INT a = 10;
INT b = 100;
cout << "a + b:" << a + b << endl;
return 0;
}
有點巧,這和前面學的Vim裡面的替換基本一樣。
- 讀取指定檔案,删除所有帶有某個關鍵詞的行:
[email protected]:~/shell$ sed '/int/d' nowcoder.txt
#include <iostream>
using namespace std;
{
cout << "a + b:" << a + b << endl;
return 0;
}
- 讀取指定檔案,在第4行後插入一行新内容:
[email protected]:~/shell$ sed -e '4a\ cout << "hello world" << end;' nowcoder.
txt
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << end;
int a = 10;
int b = 100;
cout << "a + b:" << a + b << endl;
return 0;
}
- 讀取指定檔案,在第4行後插入多行新内容:
[email protected]:~/shell$ cat nowcoder.txt | sed -e '4a\ cout << "hello world"
<< endl; \n cout << "hello aha" << endl;'
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
cout << "hello aha" << endl;
int a = 10;
int b = 100;
cout << "a + b:" << a + b << endl;
return 0;
}
- 讀取指定檔案,删除第2-5行的内容:
[email protected]:~/shell$ cat -n nowcoder.txt | sed '2,5d'
1 #include <iostream>
6 int b = 100;
7 cout << "a + b:" << a + b << endl;
8 return 0;
9 }
- 讀取指定檔案,替換第2-5行的内容:
[email protected]:~/shell$ sed '2,5c cout << "gaga" << endl;' nowcoder.txt
#include <iostream>
cout << "gaga" << endl;
int b = 100;
cout << "a + b:" << a + b << endl;
return 0;
}
- 指定讀取某個檔案的第3-7行:
[email protected]:~/shell$ sed -n '3,7p' nowcoder.txt
int main()
{
int a = 10;
int b = 100;
cout << "a + b:" << a + b << endl;
awk
:文本和資料進行處理的程式設計語言
awk
awk
指令來自于三位創始人”Alfred Aho,Peter Weinberger, Brian Kernighan “的姓氏縮寫,其功能是用于對文本和資料進行處理的程式設計語言。使用
awk
指令可以讓使用者自定義函數或正規表達式對文本内容進行高效管理,與
sed
、
grep
并稱為Linux系統中的文本三劍客。
文法格式:
awk 參數 檔案
常用參數:
參數 | 功能 |
---|---|
| 指定輸入時用到的字段分隔符 |
| 自定義變量 |
| 從腳本中讀取 指令 |
| 對 值設定内在限制 |
常用的
awk
内置變量:
awk
文法由一系列條件和動作組成,在花括号内可以有多個動作,多個動作之間用分号分隔,在多個條件和動作之間可以有若幹空格,也可以沒有。
變量名稱 | 說明 |
---|---|
| 目前輸入文檔的檔案名 |
| 目前輸入文檔的目前行号,尤其當多個輸入文檔時有用 |
| 設定字段分隔符,預設為空格或制表符 |
| 目前記錄(行)的字段(列)個數 |
| 輸入資料流的目前記錄數(行号) |
| 輸出字段分隔符,預設為空格 |
| 輸出記錄分隔符,預設為換行符 |
| 輸入記錄分隔符,預設為換行符 |
是一種處理文本檔案的程式設計語言,檔案的每行資料都被稱為記錄,預設以空格或制表符為分隔符,每條記錄被分成若幹字段(列),
awk
每次從檔案中讀取一條記錄。
awk
例子:
- 僅顯示指定檔案中第1、2列的内容(預設以空格為間隔符):
[email protected]:~/shell$ awk '{print $1,$2}' nowcoder.txt
#include <iostream>
using namespace
int main()
{
int a
int b
cout <<
return 0;
}
- 以冒号為間隔符,僅顯示指定檔案中第1列的内容:
[email protected]:~/shell$ awk -F : '{print $1,$2}' /etc/passwd
root x
daemon x
bin x
...
tcpdump x
sshd x
landscape x
pollinate x
lucky x
檔案中的内容由
/etc/passwd
分隔開。
:
- 以冒号為間隔符,顯示系統中所有UID号碼大于500的使用者資訊(第3列):
[email protected]:~/shell$ awk -F : '$3>=500' /etc/passwd
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
lucky:x:1000:1000:,,,:/home/lucky:/bin/bash
- 僅顯示指定檔案中含有指定關鍵詞
的内容:main
[email protected]:~/shell$ awk '/main/{print}' nowcoder.txt
int main()
- 以冒号為間隔符,僅顯示指定檔案中最後一個字段的内容:
[email protected]:~/shell$ awk -F : '{print $NF}' /etc/passwd
/bin/bash
/usr/sbin/nologin
/usr/sbin/nologin
...
/usr/sbin/nologin
/bin/false
/bin/bash
- 輸出行号,
将所有檔案的資料視為一個資料流,而NR
則是将多個檔案的資料視為獨立的若幹個資料流,遇到新檔案時行号從1開始重新遞增。FNR
[email protected]:~$ awk '{print NR}' first.txt three.sh
1
2
3
[email protected]:~$ awk '{print FNR}' first.txt three.sh
1
1
2
grep
:強大的文本搜尋工具
grep
grep
來自于英文詞組“global search regular expression and print out the line”的縮寫,意思是用于全面搜尋的正規表達式,并将結果輸出。人們通常會将
grep
指令與正規表達式搭配使用,參數作為搜尋過程中的補充或對輸出結果的篩選,指令模式十分靈活。
與之容易混淆的是
egrep
指令和
fgrep
指令。如果把
grep
指令當作是标準搜尋指令,那麼
egrep
則是擴充搜尋指令,等價于“
grep -E
”指令,支援擴充的正規表達式。而
fgrep
則是快速搜尋指令,等價于“
grep -F
”指令,不支援正規表達式,直接按照字元串内容進行比對。
文法格式:
grep [參數] 檔案
常用參數:
參數 | 功能 |
---|---|
| 忽略大小寫 |
| 隻輸出比對行的數量 |
| 隻列出符合比對的檔案名,不列出具體的比對行 |
| 列出所有的比對行,顯示行号 |
| 查詢多檔案時不顯示檔案名 |
| 不顯示不存在、沒有比對文本的錯誤資訊 |
| 顯示不包含比對文本的所有行 |
| 比對整詞 |
| 比對整行 |
| 遞歸搜尋 |
| 禁止輸出任何結果,已退出狀态表示搜尋是否成功 |
| 列印比對行距檔案頭部的偏移量,以位元組為機關 |
| 與 結合使用,列印比對的詞據檔案頭部的偏移量,以位元組為機關 |
| 比對固定字元串的内容 |
| 支援擴充的正規表達式 |
- 搜尋某個檔案中,包含某個關鍵詞的内容:
[email protected]:~/shell$ grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
- 搜尋某個檔案中,以某個關鍵詞開頭的内容:
[email protected]:~/shell$ grep ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
- 搜尋多個檔案中,包含某個關鍵詞的内容:
[email protected]:~# grep lucky /etc/passwd /etc/shadow
/etc/passwd:lucky:x:1000:1000:,,,:/home/lucky:/bin/bash
/etc/shadow:lucky:$6$SBxuPYFLSnBcfbHN$OkFFnnJCpf2P4OLOnnaWXMq.xbmgL3H5aRy4nkEkk/.8VHABaKDS6MdYm3UR3TpHZplAl5HVyffI8nbLlAAoh1:19256:0:99999:7:::
- 搜尋多個檔案中,包含某個關鍵詞的内容,不顯示檔案名稱:
[email protected]:~# grep -h lucky /etc/passwd /etc/shadow
lucky:x:1000:1000:,,,:/home/lucky:/bin/bash
lucky:$6$SBxuPYFLSnBcfbHN$OkFFnnJCpf2P4OLOnnaWXMq.xbmgL3H5aRy4nkEkk/.8VHABaKDS6MdYm3UR3TpHZplAl5HVyffI8nbLlAAoh1:19256:0:99999:7:::
- 輸出在某個檔案中,包含某個關鍵詞行的數量:
[email protected]:~# grep -c root /etc/passwd /etc/shadow
/etc/passwd:1
/etc/shadow:1
- 搜尋某個檔案中,包含某個關鍵詞位置的行号及内容:
[email protected]:~/shell$ grep -n int nowcoder.txt
3:int main()
5: int a = 10;
6: int b = 100;
- 搜尋某個檔案中,不包含某個關鍵詞的内容:
[email protected]:~/shell$ grep -v int nowcoder.txt
#include <iostream>
using namespace std;
{
cout << "a + b:" << a + b << endl;
return 0;
}
- 搜尋目前工作目錄中,包含某個關鍵詞内容的檔案,未找到則提示:
[email protected]:/# grep -l root *
grep: bin: Is a directory
grep: boot: Is a directory
grep: dev: Is a directory
grep: etc: Is a directory
grep: home: Is a directory
init
grep: lib: Is a directory
grep: lib32: Is a directory
...
grep: tmp: Is a directory
grep: usr: Is a directory
grep: var: Is a directory
- 搜尋目前工作目錄中,包含某個關鍵詞内容的檔案,未找到不提示:
[email protected]:~/shell$ grep -sl main *
nowcoder.txt
- 遞歸搜尋,不僅搜尋指定目錄,還搜尋其内子目錄内是否有關鍵詞檔案:
[email protected]:/# grep -srl root /etc
/etc/services
/etc/ltrace.conf
/etc/systemd/logind.conf
/etc/crontab
/etc/newt/palette.ubuntu
/etc/xattr.conf
/etc/apparmor.d/tunables/home
...
- 搜尋某個檔案中,精準比對到某個關鍵詞的内容(搜尋詞應與整行内容完全一樣才會顯示,有别于一般搜尋):
[email protected]:~/shell$ grep -x "return 0;" nowcoder.txt
[email protected]:~/shell$ grep -x " return 0;" nowcoder.txt
return 0;
- 判斷某個檔案中,是否包含某個關鍵詞,通過傳回狀态值輸出結果(0為包含,1為不包含),友善在Shell腳本中判斷和調用:
[email protected]:~/shell$ grep -q return nowcoder.txt
[email protected]:~/shell$ echo $? # 包含
0
[email protected]:~/shell$ grep -q returns nowcoder.txt
[email protected]:~/shell$ echo $? # 不包含
1
- 搜尋某個檔案中,空行的數量:
[email protected]:~/shell$ grep -c ^$ nowcoder.txt
0
tr
:字元轉換工具
tr
tr
指令來自于英文單詞transform的縮寫,中文譯為轉換,其功能是用于字元轉換。
tr
指令是一款批量字元轉換、壓縮、删除的文本工具,但僅能從标準輸入中讀取文本内容,需要與管道符或輸入重定向操作符搭配使用。
文法格式:
tr [參數] 字元串1 字元串2
常用參數:
| 反選字元串1的補集(取反) |
---|---|
| 删除字元串1中出現的所有字元 |
| 删除所有重複出現的字元序列 |
将指定檔案中的小寫字母轉換成大寫字母後輸出内容到終端界面:
[email protected]:~$ tr [a-z] [A-Z] < hello.py
ABC
567
AAA
BBB
CCC
删除指定檔案中所有的數字後輸出内容到終端界面:
[email protected]:~$ tr -d [0-9] < hello.py
abc
aaa
bbb
ccc
将指定檔案中的多個相鄰空行去重後輸出内容到終端界面:
[email protected]:~$ tr -s "[\n]" < nowcoder.txt
abc
567
aaa
bbb
ccc
正則文法學習
正規表達式為進階的文本模式比對、抽取、與/或文本形式的搜尋和替換功能提供了基礎。簡單地說,正規表達式是一些由字元和特殊符号組成的字元串,它們描述了模式的重複或者表述多個字元,于是正規表達式能按照某種模式比對一系列有相似特征的字元串。
把标準字母表用于通用文本,我們展示了一些簡單的正規表達式以及這些模式所表述的字元串。下面所介紹的正規表達式都是最基本、最普通的。它們僅僅用一個簡單的字元串構造成一個比對字元串的模式:該字元串由正規表達式定義。
正規表達式模式 | 比對的字元串 |
---|---|
foo | foo |
abc123 | abc123 |
Python | Python |
上面的第一個正規表達式模式是“
foo
”。該模式沒有使用任何特殊符号去比對其他符号,而隻比對所描述的内容,是以,能夠比對這個模式的隻有包含“
foo
”的字元串。同理,對于字元串“
Python
”和“
abc123
”也一樣。
正規表達式的強大之處在于引入特殊字元來定義字元集、比對子組和重複模式。正是由于這些特殊符号,使得正規表達式可以比對字元串集合,而不僅僅隻是某單個字元串。下面列出最常見的符号和字元。
符号:
表示法 | 描述 | 正規表達式示例 |
---|---|---|
| 比對文本字元串的字面值 | foo |
| 比對正規表達式 或者 | foo|bar |
| 比對任何字元(除了 之外) | b.b |
| 比對字元串起始部分 | ^Dear |
| 比對字元串終止部分 | /bin/*sh$ |
| 比對 0 次或者多次前面出現的正規表達式 | [A-Za-z0-9]* |
| 比對 1 次或者多次前面出現的正規表達式 | [a-z]+.com |
| 比對 0 次或者 1 次前面出現的正規表達式 | goo? |
| 比對 N 次前面出現的正規表達式 | [0-9]{3} |
| 比對 M~N 次前面出現的正規表達式 | [0-9]{5,9} |
| 比對來自字元集的任意單一字元 | [aeiou] |
| 比對 x~y 範圍中的任意單一字元 | [0-9], [A-Za-z] |
| 不比對此字元集中出現的任何一個字元,包括某一範圍的字元 | [^aeiou], [^A-Za-z0-9] |
特殊字元
表示法 | 描述 | 正規表達式示例 |
---|---|---|
| 比對任何十進制數字,與 一緻( 與 相反,不比對任何非數值型的數字) | data\d+.txt |
| 比對任何字母數字字元,與 相同 ( 與之相反) | [A-Za-z_]\w+ |
| 比對任何空格字元,與 相同( 與之相反) | of\sthe |
| 比對任何單詞邊界( 與之相反) | |
比對已儲存的子組 | price:\16 | |
| 逐字比對任何特殊字元 (即僅按照字面意義比對,不比對特殊含義) | .,\,* |
( ) | 比對字元串的起始(結束) | \ADear |
【舉例】比對空行,如下兩個正規表達式比對空行:
- “
”:表示空行^$
- “
”:表示空行以及全是空格的行^[ ]*$
題目解決方案
方法一: grep
搭配正則使用
grep
排除檔案中符合表達式的行,并顯示其他行
[email protected]:~$ grep -v '^$' nowcoder.txt
abc
567
aaa
bbb
ccc
或者搭配管道符一起使用:
[email protected]:~$ cat nowcoder.txt | grep -v '^$'
abc
567
aaa
bbb
ccc
方法二: sed
正則删除
sed
[email protected]:~$ sed '/^$/d' nowcoder.txt
abc
567
aaa
bbb
ccc
d
是删除指令。
方法三: awk
正則過濾
awk
[email protected]:~$ awk '!/^$/{print $NF}' nowcoder.txt
abc
567
aaa
bbb
ccc
方法四: awk
條件
awk
[email protected]:~$ awk '{if(!/^$/) print $0}' nowcoder.txt
abc
567
aaa
bbb
ccc
或者搭配管道符一起使用
[email protected]:~$ cat nowcoder.txt | awk '{if(!/^$/) print $0}'
abc
567
aaa
bbb
ccc
表示否定
!
表示輸出行間所有内容
$0
直接判斷
$0
也可以:
[email protected]:~$ awk '{if($0 != "") print $0}' nowcoder.txt
abc
567
aaa
bbb
ccc
方法五: while
循環
while
while read line
do
if [[ ! -z $line ]]
then
echo $line
fi
done < nowcoder.txt
方法六: tr
指令
tr
[email protected]:~$ cat nowcoder.txt | tr -s "\n"
abc
567
aaa
bbb
ccc
或者
[email protected]:~$ tr -s "[\n]" < nowcoder.txt
abc
567
aaa
bbb
ccc