一、常用操作以及概念
求助
1. --help
指令的基本用法與選項介紹。
2. man
man 是 manual 的縮寫,將指令的具體信息顯示出來。
當執行 man date 時,有 DATE(1) 出現,其中的數字代表指令的類型,常用的數字及其類型如下:
代號 | 類型 |
---|---|
1 | 用戶在 shell 環境中可以操作的指令或者可執行文件 |
5 | 配置文件 |
8 | 系統管理員可以使用的管理指令 |
3. info
info 與 man 類似,但是 info 將文檔分成一個個頁面,每個頁面可以進行跳轉。
關機
1. sync
爲了加快對磁盤文件的讀寫速度,位於內存中的文件數據不會立即同步到磁盤上,因此關機之前需要先進行 sync 同步操作。
2. shutdown
# /sbin/shutdown [-krhc] [時間] [警告訊息] -k : 不會關機,只是發送警告訊息,通知所有在線的用戶 -r : 將系統的服務停掉後就重新啓動 -h : 將系統的服務停掉後就立即關機 -c : 取消已經在進行的 shutdown 指令內容
3. 其它關機指令
reboot、halt、poweroff。
PATH
可以在環境變量 PATH 中聲明可執行文件的路徑,路徑之間用 : 分隔。
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
運行等級
- 0:關機模式
- 1:單用戶模式(可用於破解 root 密碼)
- 2:無網絡支持的多用戶模式
- 3:有網絡支持的多用戶模式(文本模式,工作中最常用的模式)
- 4:保留,未使用
- 5:有網絡支持的 X-windows 多用戶模式(桌面)
- 6:重新引導系統,即重啓
sudo
使用 sudo 允許一般用戶使用 root 可執行的命令,只有在 /etc/sudoers 配置文件中添加的用戶才能使用該指令。
GNU
GNU 計劃,譯爲革奴計劃,它的目標是創建一套完全自由的操作系統,稱爲 GNU,其內容軟件完全以 GPL 方式發佈。其中 GPL 全稱爲 GNU 通用公共許可協議,包含了以下內容:
- 以任何目的運行此程序的自由;
- 再複製的自由;
- 改進此程序,並公開發布改進的自由。
包管理工具
RPM 和 DPKG 爲最常見的兩類軟件包管理工具。RPM 全稱爲 Redhat Package Manager,最早由 Red Hat 公司制定實施,隨後被 GNU 開源操作系統接受併成爲很多 Linux 系統 (RHEL) 的既定軟件標準。與 RPM 進行競爭的是基於 Debian 操作系統 (UBUNTU) 的 DEB 軟件包管理工具- DPKG,全稱爲 Debian Package,功能方面與 RPM 相似。
YUM 基於 RPM 包管理工具,具有依賴管理功能,並具有軟件升級的功能。
發行版
Linux 發行版是 Linux 內核及各種應用軟件的集成版本。
基於的包管理工具 | 商業發行版 | 社區發行版 |
---|---|---|
DPKG | Ubuntu | Debian |
RPM | Red Hat | Fedora / CentOS |
VIM 三個模式
- 一般指令模式(Command mode):進入 VIM 的默認模式,可以用於移動遊標查看內容;
- 編輯模式(Insert mode):按下 "i" 等按鍵之後進入,可以對文本進行編輯;
- 指令列模式(Bottom-line mode):按下 ":" 按鍵之後進入,用於保存退出等操作。
在指令列模式下,有以下命令用於離開或者保存文件。
命令 | 作用 |
---|---|
:w | 寫入磁盤 |
:w! | 當文件爲只讀時,強制寫入磁盤。到底能不能寫入,與用戶對該文件的權限有關 |
:q | 離開 |
:q! | 強制離開不保存 |
:wq | 寫入磁盤後離開 |
:wq! | 強制寫入磁盤後離開 |
二、分區
磁盤的文件名
Linux 中每個硬件都被當做一個文件,包括磁盤。常見磁盤的文件名如下:
- SCSI/SATA/USB 磁盤:/dev/sd[a-p]
- IDE 磁盤:/dev/hd[a-d]
其中文件名後面的序號的確定與磁盤插入的順序有關,而與磁盤所插入的插槽位置無關。
分區表
磁盤分區表主要有兩種格式,一種是限制較多的 MBR 分區表,一種是較新且限制較少的 GPT 分區表。
1. MBR
MBR 中,第一個扇區最重要,裏面有主要開機記錄(Master boot record, MBR)及分區表(partition table),其中 MBR 佔 446 bytes,分區表佔 64 bytes。
分區表只有 64 bytes,最多隻能存儲 4 個分區,這 4 個分區爲主分區(Primary)和擴展分區(Extended)。其中擴展分區只有一個,它將其它空間用來記錄分區表,因此通過擴展分區可以分出更多分區,這些分區稱爲邏輯分區。
Linux 也把分區當成文件,分區文件的命名方式爲:磁盤文件名 + 編號,例如 /dev/sda1。注意,邏輯分區的編號從 5 開始。
2. GPT
不同的磁盤有不同的扇區大小,例如 512 bytes 和最新磁盤的 4 k。GPT 爲了兼容所有磁盤,在定義扇區上使用邏輯區塊地址(Logical Block Address, LBA)。
GPT 第 1 個區塊記錄了 MBR,緊接着是 33 個區塊記錄分區信息,並把最後的 33 個區塊用於對分區信息進行備份。
GPT 沒有擴展分區概念,都是主分區,最多可以分 128 個分區。
開機檢測程序
1. BIOS
BIOS 是開機的時候計算機執行的第一個程序,這個程序知道可以開機的磁盤,並讀取磁盤第一個扇區的 MBR,由 MBR 執行其中的開機管理程序,這個開機管理程序會加載操作系統的核心文件。
MBR 中的開機管理程序提供以下功能:選單、載入核心文件以及轉交其它開機管理程序。轉交這個功能可以用來實現了多重引導,只需要將另一個操作系統的開機管理程序安裝在其它分區的啓動扇區上,在啓動 MBR 中的開機管理程序時,就可以選擇啓動當前的操作系統或者轉交給其它開機管理程序從而啓動另一個操作系統。
安裝多重引導,最好先安裝 Windows 再安裝 Linux。因爲安裝 Windows 時會覆蓋掉 MBR,而 Linux 可以選擇將開機管理程序安裝在 MBR 或者其它分區的啓動扇區,並且可以設置開機管理程序的選單。
2. UEFI
UEFI 相比於 BIOS 來說功能更爲全面,也更爲安全。
掛載
掛載利用目錄作爲分區的進入點,也就是說,進入目錄之後就可以讀取分區的數據。
三、文件
文件權限概念
把用戶分爲三種:文件擁有者、羣組以及其它人,對不同的用戶有不同的文件權限。
使用 ls 查看一個文件時,會顯示一個文件的信息,例如 drwxr-xr-x. 3 root root 17 May 6 00:14 .config
,對這個信息的解釋如下:
- drwxr-xr-x:文件類型以及權限,第 1 位爲文件類型字段,後 9 位爲文件權限字段。
- 3:鏈接數;
- root:文件擁有者;
- root:所屬羣組;
- 17:文件大小;
- May 6 00:14:文件最後被修改的時間;
- .config:文件名。
常見的文件類型及其含義有:
- d:目錄;
- -:文件;
- l:鏈接文件;
9 位的文件權限字段中,每 3 個爲一組,共 3 組,每一組分別代表對文件擁有者、所屬羣組以及其它人的文件權限。一組權限中的 3 位分別爲 r、w、x 權限,表示可讀、可寫、可執行。
文件屬性以及權限的修改
1. 修改文件所屬羣組
# chgrp [-R] groupname dirname/filename -R:遞歸修改
2. 修改文件擁有者
不僅可以修改文件擁有者,也可以修改文件所屬羣組。
# chown [-R] 用戶名:羣組名 dirname/filename
3. 修改權限
可以將一組權限用數字來表示,此時一組權限的 3 個位當做二進制數字的位,從左到右每個位的權值爲 4、2、1,即每個權限對應的數字權值爲 r : 4、w : 2、x : 1。
# chmod [-R] xyz dirname/filename
範例:將 .bashrc 文件的權限修改爲 -rwxr-xr--。
# chmod 754 .bashrc
也可以使用符號來設定權限。
# chmod [ugoa] [+-=] [rwx] dirname/filename - u:擁有者 - g:所屬羣組 - o:其他人 - a:所有人 - +:添加權限 - -:移除權限 - =:設定權限
範例:爲 .bashrc 文件的所有用戶添加寫權限。
# chmod a+w .bashrc
目錄的權限
文件名不是存儲在一個文件的內容中,而是存儲在一個文件所在的目錄中。因此,擁有文件的 w 權限並不能對文件名進行修改。
目錄存儲文件列表,一個目錄的權限也就是對其文件列表的權限。因此,目錄的 r 權限表示可以讀取文件列表;w 權限表示可以修改文件列表,具體來說,就是添加刪除文件,對文件名進行修改;x 權限可以讓該目錄成爲工作目錄,x 權限是 r 和 w 權限的基礎,如果不能使一個目錄成爲工作目錄,也就沒辦法讀取文件列表以及對文件列表進行修改了。
文件默認權限
- 文件默認權限:文件默認沒有可執行權限,因此爲 666,也就是 -rw-rw-rw- 。
- 目錄默認權限:目錄必須要能夠進入,也就是必須擁有可執行權限,因此爲 777 ,也就是 drwxrwxrwx。
可以通過 umask 設置或者查看文件的默認權限,通常以掩碼的形式來表示,例如 002 表示其它用戶的權限去除了一個 2 的權限,也就是寫權限,因此建立新文件時默認的權限爲 -rw-rw-r-- 。
目錄配置
爲了使不同 Linux 發行版本的目錄結構保持一致性,Filesystem Hierarchy Standard (FHS) 規定了 Linux 的目錄結構。最基礎的三個目錄如下:
- / (root, 根目錄)
- /usr (unix software resource):所有系統默認軟件都會安裝到這個目錄;
- /var (variable):存放系統或程序運行過程中的數據文件。
文件時間
- modification time (mtime):文件的內容更新就會更新;
- status time (ctime):文件的狀態(權限、屬性)更新就會更新;
- access time (atime):讀取文件時就會更新。
文件與目錄的基本操作
1. ls
列出文件或者目錄的信息,目錄的信息就是其中包含的文件。
# ls [-aAdfFhilnrRSt] file|dir -a :列出全部的文件 -d :僅列出目錄本身 -l :以長數據串行列出,包含文件的屬性與權限等等數據
2. cp
複製操作。
如果源文件有兩個以上,則目的文件一定要是目錄纔行。
cp [-adfilprsu] source destination -a :相當於 -dr --preserve=all 的意思,至於 dr 請參考下列說明 -d :若來源文件爲鏈接文件,則複製鏈接文件屬性而非文件本身 -i :若目標文件已經存在時,在覆蓋前會先詢問 -p :連同文件的屬性一起復制過去 -r :遞歸持續複製 -u :destination 比 source 舊才更新 destination,或 destination 不存在的情況下才複製 --preserve=all :除了 -p 的權限相關參數外,還加入 SELinux 的屬性, links, xattr 等也複製了
3. rm
移除操作。
# rm [-fir] 文件或目錄 -r :遞歸刪除
4. mv
移動操作。
# mv [-fiu] source destination # mv [options] source1 source2 source3 .... directory -f : force 強制的意思,如果目標文件已經存在,不會詢問而直接覆蓋
獲取文件內容
1. cat
取得文件內容。
# cat [-AbEnTv] filename -n :打印出行號,連同空白行也會有行號,-b 不會
2. tac
是 cat 的反向操作,從最後一行開始打印。
3. more
可以一頁一頁查看文件內容,和文本編輯器類似。
4. less
和 more 類似。
5. head
可以取得文件前幾行。
# head [-n number] filename -n :後面接數字,代表顯示幾行的意思
6. tail
是 head 的反向操作,只是取得是後幾行。
7. od
可以以字符或者十六進制的形式顯示二進制文件。
8. touch
修改文件時間或者建立新文件。
# touch [-acdmt] filename -a : 更新 atime -c : 更新 ctime,若該文件不存在則不建立新文件 -m : 更新 mtime -d : 後面可以接更新日期而不使用當前日期,也可以使用 --date="日期或時間" -t : 後面可以接更新時間而不使用當前時間,格式爲[YYYYMMDDhhmm]
指令與文件搜索
1. which
指令搜索。
# which [-a] command -a :將所有指令列出,而不是隻列第一個
2. whereis
whereis 搜索文件的速度比較快,因爲它只搜索幾個特定的目錄。
# whereis [-bmsu] dirname/filename
3. locate
locate 可以用關鍵字或者正則表達式進行搜索。
locate 使用 /var/lib/mlocate/ 這個數據庫來進行搜索,它存儲在內存中,並且每天更新一次,所以無法用 locate 搜索新建的文件。可以使用 updatedb 來立即更新數據庫。
# locate [-ir] keyword -r:接正則表達式
4. find
find 可以使用文件的屬性和權限進行搜索。
# find [basedir] [option] example: find . -name "shadow*"
(一)與時間有關的選項
-mtime n :列出在 n 天前的那一天修改過內容的文件 -mtime +n :列出在 n 天之前 (不含 n 天本身) 修改過內容的文件 -mtime -n :列出在 n 天之內 (含 n 天本身) 修改過內容的文件 -newer file : 列出比 file 更新的文件
+4、4 和 -4 的指示的時間範圍如下:
(二)與文件擁有者和所屬羣組有關的選項
-uid n -gid n -user name -group name -nouser :搜索擁有者不存在 /etc/passwd 的文件 -nogroup:搜索所屬羣組不存在於 /etc/group 的文件
(三)與文件權限和名稱有關的選項
-name filename -size [+-]SIZE:搜尋比 SIZE 還要大 (+) 或小 (-) 的文件。這個 SIZE 的規格有:c: 代表 byte,k: 代表 1024bytes。所以,要找比 50KB 還要大的文件,就是 -size +50k -type TYPE -perm mode :搜索權限等於 mode 的文件 -perm -mode :搜索權限包含 mode 的文件 -perm /mode :搜索權限包含任一 mode 的文件
四、磁盤與文件系統
文件系統的組成
對分區進行格式化是爲了在分區上建立文件系統。一個分區通常只能格式化爲一個文件系統,但是磁盤陣列等技術可以將一個分區格式化爲多個文件系統,因此只有文件系統能被掛載,而分區不能被掛載。
文件系統有以下三個結構:
- superblock:記錄文件系統的整體信息,包括 inode 和 block 的總量、使用量、剩餘量,以及文件系統的格式與相關信息等;
- inode:一個文件佔用一個 inode,記錄文件的屬性,同時記錄此文件的內容所在的 block 號碼;
- block:記錄文件的內容,文件太大時,會佔用多個 block。
當要讀取一個文件的內容時,先在 inode 中去查找文件內容所在的所有 block,然後把所有 block 的內容讀出來。
磁盤碎片是指一個文件內容所在的 block 過於分散。
inode
Ext2 文件系統支持的 block 大小有 1k、2k 和 4k 三種,不同的 block 大小限制了單一文件的大小。而每個 inode 大小是固定爲 128 bytes。
inode 中記錄了文件內容所在的 block,但是每個 block 非常小,一個大文件隨便都需要幾十萬的 block。而一個 inode 大小有限,無法直接引用這麼多 block。因此引入了間接、雙間接、三間接引用。間接引用是指,讓 inode 記錄的引用 block 塊當成 inode 用來記錄引用信息。
inode 具體包含以下信息:
- 該文件的存取模式 (read/write/excute);
- 該文件的擁有者與羣組 (owner/group);
- 該文件的容量;
- 該文件建立或狀態改變的時間 (ctime);
- 最近一次的讀取時間 (atime);
- 最近修改的時間 (mtime);
- 定義文件特性的旗標 (flag),如 SetUID...;
- 該文件真正內容的指向 (pointer)。
目錄的 inode 與 block
建立一個目錄時,會分配一個 inode 與至少一個 block。block 記錄的內容是目錄下所有文件的 inode 編號以及文件名。可以看出文件的 inode 本身不記錄文件名,文件名記錄在目錄中,因此新增文件、刪除文件、更改文件名這些操作與目錄的 w 權限有關。
實體鏈接與符號鏈接
# ln [-sf] source_filename dist_filename -s :默認是 hard link,加 -s 爲 symbolic link -f :如果目標文件存在時,先刪除目標文件
1. 實體鏈接
它和普通文件類似,實體鏈接文件的 inode 都指向源文件所在的 block 上,也就是說讀取文件直接從源文件的 block 上讀取。
刪除任意一個條目,文件還是存在,只要引用數量不爲 0。
有以下限制:不能跨越 File System、不能對目錄進行鏈接。
# ln /etc/crontab . # ll -i /etc/crontab crontab 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab
2. 符號鏈接
符號鏈接文件保存着源文件所在的絕對路徑,在讀取時會定位到源文件上,可以理解爲 Windows 的快捷方式。
當源文件被刪除了,鏈接文件就打不開了。
可以爲目錄建立鏈接。
# ll -i /etc/crontab /root/crontab2 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab 53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab
五、壓縮與打包
壓縮
Linux 底下有很多壓縮文件的擴展名,常見的如下:
擴展名 | 壓縮程序 |
---|---|
*.Z | compress |
*.zip | zip |
*.gz | gzip |
*.bz2 | bzip2 |
*.xz | xz |
*.tar | tar 程序打包的數據,沒有經過壓縮 |
*.tar.gz | tar 程序打包的文件,經過 gzip 的壓縮 |
*.tar.bz2 | tar 程序打包的文件,經過 bzip2 的壓縮 |
*.tar.xz | tar 程序打包的文件,經過 xz 的壓縮 |
1. gzip
gzip 是 Linux 使用最廣的壓縮指令,可以解開 compress、zip 與 gzip 所壓縮的文件。
經過 gzip 壓縮過,源文件就不存在了。
有 9 個不同的壓縮等級可以使用。
可以使用 zcat、zmore、zless 來讀取壓縮文件的內容。
$ gzip [-cdtv#] filename -c :將壓縮的數據輸出到屏幕上 -d :解壓縮 -t :檢驗壓縮文件是否出錯 -v :顯示壓縮比等信息 -# : # 爲數字的意思,代表壓縮等級,數字越大壓縮比越高,默認爲 6
2. bzip2
提供比 gzip 更高的壓縮比。
查看命令:bzcat、bzmore、bzless、bzgrep。
$ bzip2 [-cdkzv#] filename -k :保留源文件
3. xz
提供比 bzip2 更佳的壓縮比。
可以看到,gzip、bzip2、xz 的壓縮比不斷優化。不過要注意的是,壓縮比越高,壓縮的時間也越長。
查看命令:xzcat、xzmore、xzless、xzgrep。
$ xz [-dtlkc#] filename
打包
壓縮指令只能對一個文件進行壓縮,而打包能夠將多個文件打包成一個大文件。tar 不僅可以用於打包,也可以使用 gip、bzip2、xz 將打包文件進行壓縮。
$ tar [-z|-j|-J] [cv] [-f 新建的 tar 文件] filename... ==打包壓縮 $ tar [-z|-j|-J] [tv] [-f 已有的 tar 文件] ==查看 $ tar [-z|-j|-J] [xv] [-f 已有的 tar 文件] [-C 目錄] ==解壓縮 -z :使用 zip; -j :使用 bzip2; -J :使用 xz; -c :新建打包文件; -t :查看打包文件裏面有哪些文件; -x :解打包或解壓縮的功能; -v :在壓縮/解壓縮的過程中,顯示正在處理的文件名; -f : filename:要處理的文件; -C 目錄 : 在特定目錄解壓縮。
使用方式 | 命令 |
---|---|
打包壓縮 | tar -jcv -f filename.tar.bz2 要被壓縮的文件或目錄名稱 |
查 看 | tar -jtv -f filename.tar.bz2 |
解壓縮 | tar -jxv -f filename.tar.bz2 -C 要解壓縮的目錄 |
六、Bash
可以通過 Shell 請求內核提供服務,Bash 正是 Shell 的一種。
特性
命令歷史:記錄使用過的命令。本次登錄所執行的命令都會暫時存放到內存中,~/.bash_history 文件中記錄的是前一次登錄所執行過的命令。
命令與文件補全:快捷鍵:tab。
命名別名:例如 lm 是 ls -al 的別名。
shell scripts。
通配符:例如 ls -l /usr/bin/X* 列出 /usr/bin 下面所有以 X 開頭的文件。
變量操作
- 對一個變量賦值直接使用 = ;
- 對變量取用需要在變量前加上 $ ,也可以用 ${} 的形式;
- 輸出變量使用 echo 命令。
$ var=abc $ echo $var $ echo ${var}
變量內容如果有空格,必須需要使用雙引號或者單引號。
- 雙引號內的特殊字符可以保留原本特性,例如 var="lang is $LANG",則 var 的值爲 lang is zh_TW.UTF-8;
- 單引號內的特殊字符就是特殊字符本身,例如 var='lang is $LANG',則 var 的值爲 lang is $LANG。
可以使用 `指令` 或者 $(指令) 的方式將指令的執行結果賦值給變量。例如 version=$(uname -r),則 version 的值爲 3.10.0-229.el7.x86_64。
可以使用 export 命令將自定義變量轉成環境變量,環境變量可以在子程序中使用,所謂子程序就是由當前 Bash 而產生的子 Bash。
Bash 的變量可以聲明爲數組和整數數字。注意數字類型沒有浮點數。如果不進行聲明,默認是字符串類型。變量的聲明使用 declare 命令:
$ declare [-aixr] variable -a : 定義爲數組類型 -i : 定義爲整數類型 -x : 定義爲環境變量 -r : 定義爲 readonly 類型
使用 [ ] 來對數組進行索引操作:
$ array[1]=a $ array[2]=b $ echo ${array[1]}
指令搜索順序
- 以絕對或相對路徑來執行指令,例如 /bin/ls 或者 ./ls ;
- 由別名找到該指令來執行;
- 由 Bash 內建的指令來執行;
- 按 $PATH 變量指定的搜索路徑的順序找到第一個指令來執行。
數據流重定向
重定向指的是使用文件代替標準輸入、標準輸出和標準錯誤輸出。
1 | 代碼 | 運算符 |
---|---|---|
標準輸入 (stdin) | 0 | < 或 << |
標準輸出 (stdout) | 1 | > 或 >> |
標準錯誤輸出 (stderr) | 2 | 2> 或 2>> |
其中,有一個箭頭的表示以覆蓋的方式重定向,而有兩個箭頭的表示以追加的方式重定向。
可以將不需要的標準輸出以及標準錯誤輸出重定向到 /dev/null,相當於扔進垃圾箱。
如果需要將標準輸出以及標準錯誤輸出同時重定向到一個文件,需要將某個輸出轉換爲另一個輸出,例如 2>&1 表示將標準錯誤輸出轉換爲標準輸出。
$ find /home -name .bashrc > list 2>&1
七、管線指令
管線是將一個命令的標準輸出作爲另一個命令的標準輸入,在數據需要經過多個步驟的處理之後才能得到我們想要的內容時就可以使用管線。在命令之間使用 | 分隔各個管線命令。
$ ls -al /etc | less
提取指令
cut 對數據進行切分,取出想要的部分。提取過程一行一行地進行。
$ cut -d :分隔符 -f :經過 -d 分隔後,使用 -f n 取出第 n 個區間 -c :以字符爲單位取出區間
範例 1:last 將顯示的登入者的信息,要求僅顯示用戶名。
$ last root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33) root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16) $ last | cut -d ' ' -f 1
範例 2:將 export 輸出的訊息,取得第 12 字符以後的所有字符串。
$ export declare -x HISTCONTROL="ignoredups" declare -x HISTSIZE="1000" declare -x HOME="/home/dmtsai" declare -x HOSTNAME="study.centos.vbird" .....(其他省略)..... $ export | cut -c 12
排序指令
sort 進行排序。
$ sort [-fbMnrtuk] [file or stdin] -f :忽略大小寫 -b :忽略最前面的空格 -M :以月份的名字來排序,例如 JAN,DEC -n :使用數字 -r :反向排序 -u :相當於 unique,重複的內容只出現一次 -t :分隔符,默認爲 tab -k :指定排序的區間
範例:/etc/passwd 內容是以 : 來分隔的,以第三欄來排序。
$ cat /etc/passwd | sort -t ':' -k 3 root:x:0:0:root:/root:/bin/bash dmtsai:x:1000:1000:dmtsai:/home/dmtsai:/bin/bash alex:x:1001:1002::/home/alex:/bin/bash arod:x:1002:1003::/home/arod:/bin/bash
uniq 可以將重複的數據只取一個。
$ uniq [-ic] -i :忽略大小寫 -c :進行計數
範例:取得每個人的登錄總次數
$ last | cut -d ' ' -f 1 | sort | uniq -c 1 6 (unknown 47 dmtsai 4 reboot 7 root 1 wtmp
雙向輸出重定向
輸出重定向會將輸出內容重定向到文件中,而 tee 不僅能夠完成這個功能,還能保留屏幕上的輸出。也就是說,使用 tee 指令,一個輸出會同時傳送到文件和屏幕上。
$ tee [-a] file
字符轉換指令
tr 用來刪除一行中的字符,或者對字符進行替換。
$ tr [-ds] SET1 ... -d : 刪除行中 SET1 這個字符串
範例,將 last 輸出的信息所有小寫轉換爲大寫。
$ last | tr '[a-z]' '[A-Z]'
col 將 tab 字符轉爲空格字符。
$ col [-xb] -x : 將 tab 鍵轉換成對等的空格鍵
expand 將 tab 轉換一定數量的空格,默認是 8 個。
$ expand [-t] file -t :tab 轉爲空格的數量
join 將有相同數據的那一行合併在一起。
$ join [-ti12] file1 file2 -t :分隔符,默認爲空格 -i :忽略大小寫的差異 -1 :第一個文件所用的比較字段 -2 :第二個文件所用的比較字段
paste 直接將兩行粘貼在一起。
$ paste [-d] file1 file2 -d :分隔符,默認爲 tab
分區指令
split 將一個文件劃分成多個文件。
$ split [-bl] file PREFIX -b :以大小來進行分區,可加單位,例如 b, k, m 等 -l :以行數來進行分區。 - PREFIX :分區文件的前導名稱
八、正則表達式
grep
使用正則表示式把匹配的行提取出來。
$ grep [-acinv] [--color=auto] 搜尋字符串 filename -a : 將 binary 文件以 text 文件的方式進行搜尋 -c : 計算找到個數 -i : 忽略大小寫 -n : 輸出行號 -v : 反向選擇,亦即顯示出沒有 搜尋字符串 內容的那一行 --color=auto :找到的關鍵字加顏色顯示
範例:把含有 the 字符串的行提取出來(注意默認會有 --color=auto 選項,因此以下內容在 Linux 中有顏色顯示 the 字符串)
$ grep -n 'the' regular_express.txt 8:I can't finish the test. 12:the symbol '*' is represented as start. 15:You are the best is mean you are the no. 1. 16:The world Happy is the same with "glad". 18:google is the best tools for search keyword
因爲 { 和 } 在 shell 是有特殊意義的,因此必須要使用轉義字符進行轉義。
$ grep -n 'go\{2,5\}g' regular_express.txt
printf
用於格式化輸出。
它不屬於管道命令,在給 printf 傳數據時需要使用 $( ) 形式。
$ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt) DmTsai 80 60 92 77.33 VBird 75 55 80 70.00 Ken 60 90 70 73.33
awk
可以根據字段的某些條件進行匹配,例如匹配字段小於某個值的那一行數據。
$ awk ' 條件類型 1 {動作 1} 條件類型 2 {動作 2} ...' filename
awk 每次處理一行,處理的最小單位是字段,每個字段的命名方式爲:$n,n 爲字段號,從 1 開始,$0 表示一整行。
範例 1:取出登錄用戶的用戶名和 ip
$ last -n 5 dmtsai pts/0 192.168.1.100 Tue Jul 14 17:32 still logged in dmtsai pts/0 192.168.1.100 Thu Jul 9 23:36 - 02:58 (03:22) dmtsai pts/0 192.168.1.100 Thu Jul 9 17:23 - 23:36 (06:12) dmtsai pts/0 192.168.1.100 Thu Jul 9 08:02 - 08:17 (00:14) dmtsai tty1 Fri May 29 11:55 - 12:11 (00:15) $ last -n 5 | awk '{print $1 "\t" $3}
awk 變量:
變量名稱 | 代表意義 |
---|---|
NF | 每一行擁有的字段總數 |
NR | 目前所處理的是第幾行數據 |
FS | 目前的分隔字符,默認是空格鍵 |
範例 2:輸出正在處理的行號,並顯示每一行有多少字段
$ last -n 5 | awk '{print $1 "\t lines: " NR "\t columns: " NF}' dmtsai lines: 1 columns: 10 dmtsai lines: 2 columns: 10 dmtsai lines: 3 columns: 10 dmtsai lines: 4 columns: 10 dmtsai lines: 5 columns: 9
可以使用條件,其中等於使用 ==。
範例 3:/etc/passwd 文件第三個字段爲 UID,對 UID 小於 10 的數據進行處理。
$ cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}'
root 0
bin 1
daemon 2
九、進程管理
查看進程
1. ps
查看某個時間點的進程信息
示例一:查看自己的進程
# ps -l
示例二:查看系統所有進程
# ps aux
示例三:查看特定的進程
# ps aux | grep threadx
2. top
實時顯示進程信息
示例:兩秒鐘刷新一次
# top -d 2
3. pstree
查看進程樹
示例:查看所有進程樹
# pstree -A
4. netstat
查看佔用端口的進程
# netstat -anp | grep port
進程狀態
狀態 | 說明 |
---|---|
R | running or runnable (on run queue) |
D | uninterruptible sleep (usually IO) |
S | interruptible sleep (waiting for an event to complete) |
Z | defunct/zombie, terminated but not reaped by its parent |
T | stopped, either by a job control signal or because it is being traced |
SIGCHLD
當一個子進程改變了它的狀態時:停止運行,繼續運行或者退出,有兩件事會發生在父進程中:
- 得到 SIGCHLD 信號;
- waitpid() 或者 wait() 調用會返回。
其中子進程發送的 SIGCHLD 信號包含了子進程的信息,包含了進程 ID、進程狀態、進程使用 CPU 的時間等。
在子進程退出時,它的進程描述符不會立即釋放,這是爲了讓父進程得到子進程信息。父進程通過 wait() 和 waitpid() 來獲得一個已經退出的子進程的信息。
wait()
pid_t wait(int *status)
父進程調用 wait() 會一直阻塞,直到收到一個子進程退出的 SIGCHLD 信號,之後 wait() 函數會銷燬子進程並返回。
如果成功,返回被收集的子進程的進程 ID;如果調用進程沒有子進程,調用就會失敗,此時返回 - 1,同時 errno 被置爲 ECHILD。
參數 status 用來保存被收集進程退出時的一些狀態,如果我們對這個子進程是如何死掉的毫不在意,只想把這個殭屍進程消滅掉,我們就可以設定這個參數爲 NULL:
pid = wait(NULL);
waitpid()
pid_t waitpid(pid_t pid,int *status,int options)
作用和 wait() 完全相同,但是多了兩個可由用戶控制的參數 pid 和 options。
pid 參數指示一個子進程的 ID,表示只關心這個子進程的退出 SIGCHLD 信號。如果 pid=-1 時,那麼賀 wait() 作用相同,都是關心所有子進程退出的 SIGCHLD 信號。
options 參數主要有 WNOHANG 和 WUNTRACED 兩個選項,WNOHANG 可以使 waitpid() 調用變成非阻塞的,也就是說它會立即返回,父進程可以繼續執行其它任務。
孤兒進程
一個父進程退出,而它的一個或多個子進程還在運行,那麼這些子進程將成爲孤兒進程。孤兒進程將被 init 進程(進程號爲 1)所收養,並由 init 進程對它們完成狀態收集工作。
由於孤兒進程會被 init 進程收養,所以孤兒進程不會對系統造成危害。
僵死進程
一個子進程的進程描述符在子進程退出時不會釋放,只有當父進程通過 wait() 或 waitpid() 獲取了子進程信息後纔會釋放。如果子進程退出,而父進程並沒有調用 wait() 或 waitpid(),那麼子進程的進程描述符仍然保存在系統中,這種進程稱之爲僵死進程。
僵死進程通過 ps 命令顯示出來的狀態爲 Z。
系統所能使用的進程號是有限的,如果大量的產生僵死進程,將因爲沒有可用的進程號而導致系統不能產生新的進程。
要消滅系統中大量的僵死進程,只需要將其父進程殺死,此時所有的僵死進程就會變成孤兒進程,從而被 init 所收養,這樣 init 就會釋放所有的僵死進程所佔有的資源,從而結束僵死進程。
十、I/O 複用
概念理解
I/O Multiplexing 又被稱爲 Event Driven I/O,它可以讓單個進程具有處理多個 I/O 事件的能力。
當某個 I/O 事件條件滿足時,進程會收到通知。
如果一個 Web 服務器沒有 I/O 複用,那麼每一個 Socket 連接都需要創建一個線程去處理。如果同時連接幾萬個連接,那麼就需要創建相同數量的線程。並且相比於多進程和多線程技術,I/O 複用不需要進程線程創建和切換的開銷,系統開銷更小。
I/O 模型
- 阻塞(Blocking)
- 非阻塞(Non-blocking)
- 同步(Synchronous)
- 異步(Asynchronous)
阻塞非阻塞是等待 I/O 完成的方式,阻塞要求用戶程序停止執行,直到 I/O 完成,而非阻塞在 I/O 完成之前還可以繼續執行。
同步異步是獲知 I/O 完成的方式,同步需要時刻關心 I/O 是否已經完成,異步無需主動關心,在 I/O 完成時它會收到通知。
1. 同步-阻塞
這是最常見的一種模型,用戶程序在使用 read() 時會執行系統調用從而陷入內核,之後就被阻塞直到系統調用完成。
應該注意到,在阻塞的過程中,其他程序還可以執行,因此阻塞不意味着整個操作系統都被阻塞。因爲其他程序還可以執行,因此不消耗 CPU 時間,這種模型的執行效率會比較高。
2. 同步-非阻塞
非阻塞意味着用戶程序在執行系統調用後還可以繼續執行,內核並不是馬上執行完 I/O,而是以一個錯誤碼來告知用戶程序 I/O 還未完成。爲了獲得 I/O 完成事件,用戶程序必須調用多次系統調用去詢問內核,甚至是忙等,也就是在一個循環裏面一直詢問並等待。
由於 CPU 要處理更多的用戶程序的詢問,因此這種模型的效率是比較低的。
3. 異步
該模式下,I/O 操作會立即返回,之後可以處理其它操作,並且在 I/O 完成時會收到一個通知,此時會中斷正在處理的操作,然後繼續之前的操作。
select poll epoll
這三個都是 I/O 多路複用的具體實現,select 出現的最早,之後是 poll,再是 epoll。
1. select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- fd_set 表示描述符集合;
- readset、writeset 和 exceptset 這三個參數指定讓操作系統內核測試讀、寫和異常條件的描述符;
- timeout 參數告知內核等待所指定描述符中的任何一個就緒可花多少時間;
- 成功調用返回結果大於 0;出錯返回結果爲 -1;超時返回結果爲 0。
fd_set fd_in, fd_out; struct timeval tv; // Reset the sets FD_ZERO( &fd_in ); FD_ZERO( &fd_out ); // Monitor sock1 for input events FD_SET( sock1, &fd_in ); // Monitor sock2 for output events FD_SET( sock2, &fd_out ); // Find out which socket has the largest numeric value as select requires it int largest_sock = sock1 > sock2 ? sock1 : sock2; // Wait up to 10 seconds tv.tv_sec = 10; tv.tv_usec = 0; // Call the select int ret = select( largest_sock + 1, &fd_in, &fd_out, NULL, &tv ); // Check if select actually succeed if ( ret == -1 ) // report error and abort else if ( ret == 0 ) // timeout; no event detected else { if ( FD_ISSET( sock1, &fd_in ) ) // input event on sock1 if ( FD_ISSET( sock2, &fd_out ) ) // output event on sock2 }
每次調用 select() 都需要將 fd_set *readfds, fd_set *writefds, fd_set *exceptfds 鏈表內容全部從用戶進程內存中複製到操作系統內核中,內核需要將所有 fd_set 遍歷一遍,這個過程非常低效。
返回結果中內核並沒有聲明哪些 fd_set 已經準備好了,所以如果返回值大於 0 時,程序需要遍歷所有的 fd_set 判斷哪個 I/O 已經準備好。
在 Linux 中 select 最多支持 1024 個 fd_set 同時輪詢,其中 1024 由 Linux 內核的 FD_SETSIZE 決定。如果需要打破該限制可以修改 FD_SETSIZE,然後重新編譯內核。
2. poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd { int fd; //文件描述符 short events; //監視的請求事件 short revents; //已發生的事件 };
// The structure for two events struct pollfd fds[2]; // Monitor sock1 for input fds[0].fd = sock1; fds[0].events = POLLIN; // Monitor sock2 for output fds[1].fd = sock2; fds[1].events = POLLOUT; // Wait 10 seconds int ret = poll( &fds, 2, 10000 ); // Check if poll actually succeed if ( ret == -1 ) // report error and abort else if ( ret == 0 ) // timeout; no event detected else { // If we detect the event, zero it out so we can reuse the structure if ( pfd[0].revents & POLLIN ) pfd[0].revents = 0; // input event on sock1 if ( pfd[1].revents & POLLOUT ) pfd[1].revents = 0; // output event on sock2 }
它和 select() 功能基本相同。同樣需要每次將 struct pollfd *fds 複製到內核,返回後同樣需要進行輪詢每一個 pollfd 是否已經 I/O 準備好。poll() 取消了 1024 個描述符數量上限,但是數量太大以後不能保證執行效率,因爲複製大量內存到內核十分低效,所需時間與描述符數量成正比。poll() 在 pollfd 的重複利用上比 select() 的 fd_set 會更好。
如果在多線程下,如果一個線程對某個描述符調用了 poll() 系統調用,但是另一個線程關閉了該描述符,會導致 poll() 調用結果不確定,該問題同樣出現在 select() 中。
3. epoll
int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
// Create the epoll descriptor. Only one is needed per app, and is used to monitor all sockets. // The function argument is ignored (it was not before, but now it is), so put your favorite number here int pollingfd = epoll_create( 0xCAFE ); if ( pollingfd < 0 ) // report error // Initialize the epoll structure in case more members are added in future struct epoll_event ev = { 0 }; // Associate the connection class instance with the event. You can associate anything // you want, epoll does not use this information. We store a connection class pointer, pConnection1 ev.data.ptr = pConnection1; // Monitor for input, and do not automatically rearm the descriptor after the event ev.events = EPOLLIN | EPOLLONESHOT; // Add the descriptor into the monitoring list. We can do it even if another thread is // waiting in epoll_wait - the descriptor will be properly added if ( epoll_ctl( epollfd, EPOLL_CTL_ADD, pConnection1->getSocket(), &ev ) != 0 ) // report error // Wait for up to 20 events (assuming we have added maybe 200 sockets before that it may happen) struct epoll_event pevents[ 20 ]; // Wait for 10 seconds, and retrieve less than 20 epoll_event and store them into epoll_event array int ready = epoll_wait( pollingfd, pevents, 20, 10000 ); // Check if epoll actually succeed if ( ret == -1 ) // report error and abort else if ( ret == 0 ) // timeout; no event detected else { // Check if any events detected for ( int i = 0; i < ret; i++ ) { if ( pevents[i].events & EPOLLIN ) { // Get back our connection pointer Connection * c = (Connection*) pevents[i].data.ptr; c->handleReadEvent(); } } }
epoll 僅僅適用於 Linux OS。
它是 select 和 poll 的增強版,更加靈活而且沒有描述符限制。它將用戶關心的描述符放到內核的一個事件表中,從而只需要在用戶空間和內核空間拷貝一次。
select 和 poll 方式中,進程只有在調用一定的方法後,內核纔對所有監視的描述符進行掃描。而 epoll 事先通過 epoll_ctl() 來註冊描述符,一旦基於某個描述符就緒時,內核會採用類似 callback 的回調機制,迅速激活這個描述符,當進程調用 epoll_wait() 時便得到通知。
新版本的 epoll_create(int size) 參數 size 不起任何作用,在舊版本的 epoll 中如果描述符的數量大於 size,不保證服務質量。
epoll_ctl() 執行一次系統調用,用於向內核註冊新的描述符或者是改變某個文件描述符的狀態。已註冊的描述符在內核中會被維護在一棵紅黑樹上,通過回調函數內核會將 I/O 準備好的描述符加入到一個鏈表中管理。
epoll_wait() 取出在內核中通過鏈表維護的 I/O 準備好的描述符,將他們從內核複製到程序中,不需要像 select/poll 對註冊的所有描述符遍歷一遍。
epoll 對多線程編程更有友好,同時多個線程對同一個描述符調用了 epoll_wait 也不會產生像 select/poll 的不確定情況。或者一個線程調用了 epoll_wait 另一個線程關閉了同一個描述符也不會產生不確定情況。
select 和 poll 比較
1. 功能
它們提供了幾乎相同的功能,但是在一些細節上有所不同:
- select 會修改 fd_set 參數,而 poll 不會;
- select 默認只能監聽 1024 個描述符,如果要監聽更多的話,需要修改 FD_SETSIZE 之後重新編譯;
- poll 提供了更多的事件類型。
2. 速度
poll 和 select 在速度上都很慢。
- 它們都採取輪詢的方式來找到 I/O 完成的描述符,如果描述符很多,那麼速度就會很慢;
- select 只使用每個描述符的 3 位,而 poll 通常需要使用 64 位,因此 poll 需要複製更多的內核空間。
3. 可移植性
幾乎所有的系統都支持 select,但是隻有比較新的系統支持 poll。
eopll 工作模式
epoll_event 有兩種觸發模式:LT(level trigger)和 ET(edge trigger)。
1. LT 模式
當 epoll_wait() 檢測到描述符事件發生並將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用 epoll_wait() 時,會再次響應應用程序並通知此事件。是默認的一種模式,並且同時支持 Blocking 和 No-Blocking。
2. ET 模式
當 epoll_wait() 檢測到描述符事件發生並將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用 epoll_wait() 時,不會再次響應應用程序並通知此事件。很大程度上減少了 epoll 事件被重複觸發的次數,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由於一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
select poll epoll 應用場景
很容易產生一種錯覺認爲只要用 epoll 就可以了,select poll 都是歷史遺留問題,並沒有什麼應用場景,其實並不是這樣的。
1. select 應用場景
select() poll() epoll_wait() 都有一個 timeout 參數,在 select() 中 timeout 的精確度爲 1ns,而 poll() 和 epoll_wait() 中則爲 1ms。所以 select 更加適用於實時要求更高的場景,比如核反應堆的控制。
select 歷史更加悠久,它的可移植性更好,幾乎被所有主流平臺所支持。
2. poll 應用場景
poll 沒有最大描述符數量的限制,如果平臺支持應該採用 poll 且對實時性要求並不是十分嚴格,而不是 select。
需要同時監控小於 1000 個描述符。那麼也沒有必要使用 epoll,因爲這個應用場景下並不能體現 epoll 的優勢。
需要監控的描述符狀態變化多,而且都是非常短暫的。因爲 epoll 中的所有描述符都存儲在內核中,造成每次需要對描述符的狀態改變都需要通過 epoll_ctl() 進行系統調用,頻繁系統調用降低效率。epoll 的描述符存儲在內核,不容易調試。
3. epoll 應用場景
程序只需要運行在 Linux 平臺上,有非常大量的描述符需要同時輪詢,而且這些連接最好是長連接。
4. 性能對比
參考資料
- 鳥哥. 鳥 哥 的 Linux 私 房 菜 基 礎 篇 第 三 版[J]. 2009.
- Linux 平臺上的軟件包管理
- Boost application performance using asynchronous I/O
- Synchronous and Asynchronous I/O
- Linux IO 模式及 select、poll、epoll 詳解
- poll vs select vs event-based
- Linux 之守護進程、僵死進程與孤兒進程
- Linux process states
- GUID Partition Table
- 詳解 wait 和 waitpid 函數