沒錯 "兩個月前" 不過一直沒有機會發文,所以就拖到了現在
真的對不起 (鞠躬
以下直接進入正題
exec /bin/sh
$ ((sudo fdisk -l 3>&1 1>&2 2>&3 3>&- | tee /dev/fd/2) 3>&1 1>&2 2>&3 3>&-)\
| cat >fdisk.log
在網路上看到這一段指令,相信應該很多人都不知道這段指令最後的結果是什麼
網路上很多文章都沒有很詳細的提到 redirct 和 pipe 的執行順序
直到回憶的朋友丟了一篇文章給我(網址附在文末),才有了大致上的了解
因此才有了這篇筆記
從檔案描述符 (file descriptor) 開始
在開始討論執行結果前
應該先來了解一下 file descriptor 是什麼?
很多人對其的理解: 0 是 stderr 1 是 stdout 2 是 stderr
這個是最基本的認識
不過我們應該考慮的是 "0 1 2" 代表是什麼?
檔案描述符在形式上是一個非負整數。
實際上,它是一個索引值,指向內核為每一個行程所維護的該行程開啟檔案的記錄表。當程式開啟一個現有檔案或者建立一個新檔案時,內核向行程返回一個檔案描述符。但是檔案描述符這一概念往往只適用於UNIX、Linux這樣的作業系統。
from wiki
事實上一般程式在開啟時,會自動開啟三個檔案描述符
第一個為輸入 (因此編號為0)
第二個為正常訊息輸出 (因此編號為1)
第三個為錯誤訊息輸出 (因此編號為2)
我們可以使用 lsof (list open file)指令查看
以最基本的 shell為例 (只列出相關訊息)
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 847 uesr 0u VCHR 0,84 0t208433 84 /dev/pts/0 sh 847 uesr 1u VCHR 0,84 0t208433 84 /dev/pts/0 sh 847 uesr 2u VCHR 0,84 0t208433 84 /dev/pts/0
以下解釋各欄位意義:
COMMAND: 程序名稱(指令)
PID: 程序ID
USER: 執行此指令的使用者
FD:(列舉常見的,不過回憶不是很懂)
(1) cwd: current working directory
(2) rtd: root directory
(3) txt: program text (code and data)
(4) mem: memory-mapped file
(5) mmap: memory-mapped device
(6) 數字 + 英文字: <數字為 file descriptor 編號, 英文字為鎖定模式>
(a) r : 只讀模式
(b) w: 只寫模式
(c) u : 讀寫模式
TYPE:(free BSD 中以V開頭的應為 virtual 的意思)
(1)DIR:表示目錄
(2)CHR:表示字符類型
(3)BLK:塊設備類型
(4)UNIX: UNIX 域 socket
(5)FIFO:先進先出 (First In First Out) 隊列
(6)IPv4:網際協議 (IP) socket
DEVICE: 設備號碼
SIZE/OFF: 文件大小/偏移量
NODE: inode 號碼
NAME: 打開的文件 (即 file descriptor 所指向的檔案)
什麼是 pipeline ?
管道 (pipeline) 的功用為在兩個 porcess擔任中介訊息傳遞者
在 shell 命令裡以 "|" 表示
具體功能為將其左方 process 的 1號 fd 導向 pipe
並將右方 process 的 0號 fd 導向 pipe
只不過前者是使用寫入功能,後者使用讀取功能
對於 linux 來說, pipeline 也是一種文件
我們一樣可以使用 lsof 來查看pipe
^M Suspended
$ lsof
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NAME
ls 3560 user 1u PIPE 0xfffff800029b5730 0 ->0xfffff800029b55d0
more 3561 user 0u PIPE 0xfffff800029b55d0 65536 ->0xfffff800029b5730
管道的建立先於程式執行,另外當程式完成工作後,會自己結束,
並不會等待所有程式執行完後再一同結束
什麼是重導向 (redirection)
在瞭解上述內容後,我們來試著產生 stdout 以及 stderr
首先在一個目錄下建立一個文字檔,內容隨意
$ cd test
$ echo "hello world!" > test_file
$ cat test_file none
hello world!
cat: none: No such file or directory
我們可以合理推測第一行為 stdout 第二行為 stderr
我們用以下操作來驗證:
$ cat test_file none 1>/dev/null
cat: none: No such file or directory
$ cat test_file none 2>/dev/null
hello world!
這裡說明一個特別的文件 "/dev/null"
網路上經常將其稱為黑洞
任何輸出導向 /dev/null 即為不顯示也不導向任何文件
我們再來看個稍微複雜的操作:
$ cat test_file none 2>&1 1>/dev/null
cat: none: No such file or directory
以上操作說明了 redirection 是有順序性的
"2>&1" 的意思為將 fd 2指向 fd 1所指向的文件
在第一行指令時, fd 1 和 2 都指向 /dev/null
因此在終端上是沒有輸出的
- 建議 redirection 符號 (< 、 >) 前後不要有空格,以免被shell誤判
- > 符號如果左方沒有特別指明,默認為 fd 2
- < 符號如果右方沒有特別指明,默認為 fd 0
回到最初的問題
現在我們開始來分析一開始的那行命令
括號外對FD做的更動會影響括號內的所有porcess
分析重導向命令時建議畫一個表格,這樣比較易懂
表格的上方回憶習慣標示process名稱
左方標示FD編號
另外為了方便閱讀,FD 預設指向以 "terminal" 表示
* 表示未使用
而 pipe 由左至右分別命名為 pipe1, pipe2, ....
初始狀態
fdisk | tee | cat | |
FD 0 | terminal | terminal | terminal |
FD 1 | terminal | terminal | terminal |
FD 2 | terminal | terminal | terminal |
FD 3 | * | * | * |
$ ((sudo fdisk -l 3>&1 1>&2 2>&3 3>&- | tee /dev/fd/2) 3>&1 1>&2 2>&3 3>&-) \
| cat >fdisk.log
括號外先執行,pipe 又優先於 redirection:
fdisk | tee | cat | |
FD 0 | terminal | terminal | pipe2 |
FD 1 | pipe2 | pipe2 | fdisk.log |
FD 2 | terminal | terminal | terminal |
FD 3 | * | * | * |
將標示完的部分消除:
$ ((sudo fdisk -l 3>&1 1>&2 2>&3 3>&- | tee /dev/fd/2) 3>&1 1>&2 2>&3 3>&-)
其中 3>&1 1>&2 2>&3 3>&- 的意思為 FD 1 和 FD 2 對調
<FD 3 指向 FD 1內容,FD 1 指向 FD 2內容,FD 2 指向 FD 3內容,最後將 FD 3 關閉>
fdisk | tee | cat | |
FD 0 | terminal | terminal | pipe2 |
FD 1 | terminal | terminal | fdisk.log |
FD 2 | pipe2 | pipe2 | terminal |
FD 3 | * | * | * |
處理 pipe1:
$ sudo fdisk -l 3>&1 1>&2 2>&3 3>&- | tee /dev/fd/2
fdisk | tee | cat | |
FD 0 | terminal | pipe1 | pipe2 |
FD 1 | pipe1 | terminal | fdisk.log |
FD 2 | pipe2 | pipe2 | terminal |
FD 3 | * | * | * |
接下來的命令比較特別
$ sudo fdisk -l 3>&1 1>&2 2>&3 3>&-
tee /dev/fd/2
/dev/fd/2 表示該程序的 FD 2
而 tee 對檔案描述符的操作為 "新增一個 FD 指向後面接的參數(一般為檔案),並將 FD 1內容複製一份過去"
以下操作可以當作參考:
^M Suspended
$ lsof
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NAME
ls 877 user 1u PIPE 0xfffff80002684a18 0 ->0xfffff800026848b8
tee 878 user 0u PIPE 0xfffff800026848b8 16384 ->0xfffff80002684a18
tee 878 user 1u VCHR 0,84 0t48901 /dev/pts/0
tee 878 user 2u VCHR 0,84 0t48901 /dev/pts/0
tee 878 user 3w VREG 0,78 424016351330304 / (/dev/ada0p2)
fdisk | tee | cat | |
FD 0 | terminal | pipe1 | pipe2 |
FD 1 | pipe2 | ※terminal | fdisk.log |
FD 2 | pipe1 | pipe2 | terminal |
FD 3 | * | ※pipe2 | * |
※ 得到的內容是一樣的,也就是說 fdisk 的 stderr 一樣會導向 cat 的 stdin
如果就輸出位置來討論的話,可以得到以下結果:
terminal: fdisk 的錯誤訊息、cat 的錯誤訊息
fdisk.log: fdisk 的正常訊息、fdisk 的錯誤訊息、tee 的錯誤訊息
- 括弧外
- pipe
- fd 重導向
參考資料:
http://wiki.bash-hackers.org/howto/redirection_tutorial
http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/lsof.html
沒有留言:
張貼留言