我在使用bash的時候通常會利用它的自動補全功能來看看檔案夾下的内容(連按兩下 Tab
鍵),例如:

ls
指令來确定了。
但是最近我在檢視目前shell(bash)的檔案描述符時時卻碰見一個“怪事”,當我用bash的自動補全功能檢視時,顯示為有0, 1, 2, 255, 3這五個檔案:
ls
指令來顯示fd檔案夾的時候,卻隻顯示有0, 1, 2, 255這4個檔案,3這個檔案不存在: 這是為什麼呢?
其實原因很簡單,自動補全功能是bash内置的一個功能,而
ls
是系統上的一個程式,以子程序的形式獨立于bash運作。是以如果bash這個自動補全功能打開了我們要補全的路徑(檔案夾也是檔案),那麼應該會獲得檔案描述符3, ls
也是一樣。但是5736這個PID是bash的,是以我們用ls的時候看不到3而用bash的自動補全功能看得到。
為了證明一下這個的想法,上網查了一下相關資料,了解到bash自動補全功能本身就是一個用shell語言寫的腳本,其配置在
/etc/bash_completion
這個檔案中,其中常用的内置指令是 complete
,用法為 complete -F _known_hosts xvncviewer
,即當開頭的指令./程式是 xvncviewer
的時候,如果使用者在參數上連按 Tab
鍵就會調用 _known_hosts
這個shell内置函數 ,例如: skx@lappy:~$ xvncviewer s[TAB]
savannah.gnu.org ssh.tardis.ed.ac.uk
scratchy steve.org.uk
security.debian.org security-master.debian.org
sun
skx@lappy:~$ xvncviewer sc[TAB]
我們進入
/etc/bash_completion
檔案,查找剛剛使用的
ls
指令,看看它的自動補全是什麼配置的:
complete -F _longopt a2ps awk base64 bash bc bison cat colordiff cp csplit \
cut date df diff dir du enscript env expand fmt fold gperf \
grep grub head indent irb ld ldd less ln ls m4 md5sum mkdir mkfifo mknod \
mv netstat nl nm objcopy objdump od paste pr ptx readelf rm rmdir \
sed seq sha{,1,224,256,384,512}sum shar sort split strip sum tac tail tee \
texindex touch tr uname unexpand uniq units vdir wc who
可以看到,其調用的是
_longopt
這個内置函數,繼續定位:
_longopt()
{
local cur prev words cword split
_init_completion -s || return
case "${prev,,}" in
--help|--usage|--version)
return 0
;;
--*dir*)
_filedir -d
return 0
;;
--*file*|--*path*)
_filedir
return 0
;;
--+([-a-z0-9_]))
local argtype=$( $1 --help 2>&1 | sed -ne \
"s|.*$prev\[\{0,1\}=[<[]\{0,1\}\([-A-Za-z0-9_]\{1,\}\).*|\1|p" )
case ${argtype,,} in
*dir*)
_filedir -d
return 0
;;
#......省略
可以看到
_longopt
會調用
_filedir
這個函數:
_filedir()
{
local i IFS=$'\n' xspec
_tilde "$cur" || return 0
local -a toks
local quoted x tmp
_quote_readline_by_ref "$cur" quoted
x=$( compgen -d -- "$quoted" ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
if [[ "$1" != -d ]]; then
# Munge xspec to contain uppercase version too
# http://thread.gmane.org/gmane.comp.shells.bash.bugs/15294/focus=15306
xspec=${1:+"!*.@($1|${1^^})"}
x=$( compgen -f -X "$xspec" -- $quoted ) &&
while read -r tmp; do
toks+=( "$tmp" )
done <<< "$x"
fi
# If the filter failed to produce anything, try without it if configured to
[[ -n ${COMP_FILEDIR_FALLBACK:-} && \
-n "$1" && "$1" != -d && ${#toks[@]} -lt 1 ]] && \
x=$( compgen -f -- $quoted ) &&
#......省略
可以看到該函數使用了
compgen
這個内置指令來擷取檔案夾下的檔案名(-f = "filename"),例如:
我們使用
strace
來追蹤這個内置指令的系統調用,特别是傳回檔案描述符的系統調用
open
:
通過對比可以看到
compgen
調用
open
打開了這個檔案夾,而且得到了檔案描述符3(前面的open都調用了
close
删除了它們得到的檔案描述符3)。
如果将
compgen
換成
ls
對比可以看出,
compgen
隻有一個
execve
,即
compgen
是在bash程序中執行的,但
ls
有兩個,第二個說明了它是作為bash的子程序運作的, 證明了我們之前的想法。
如果感興趣的話可以看看
ls
的源碼,其中使用到了
readdir
opendir
這兩個庫函數(GNU coreutils-8.29)
綜上,我們可以用兩個圖來總結。
自動補全:
Process: Bash
+-----------------------------+
| |
| 0,1,2,255 0,1,2,3,255|
| Tab->compgen->open |
| |
+-----------------------------+
ls
指令:
Process: Bash
+-----------------------------+
| |
| 0,1,2,255 |
| ls |
| + |
+-----------------------------+
|
|
| Child Process: ls
+------+----------------------+
| |
|0,1,2 0,1,2,3 |
| opendir->open |
+-----------------------------+
另外,如果我們将操作應用于
/proc/self/
檔案夾也會得到一些有意思的結果:
第一行我們已經講明白了,但是第二行和第三行怎麼解釋呢?
在
man 5 proc
下對這個檔案夾的解釋是這樣的:
/proc/self
This directory refers to the process accessing the /proc
filesystem, and is identical to the /proc directory named by the
process ID of the same process.
也就是說,
/proc/self/
反應的是目前通路檔案的程序的狀态資料 ,是以我們用
ls /proc/self/fd/
實際上是
ls /proc/${PID of ls}/fd/
,而
ls
會打開這個檔案夾(同時獲得3這個檔案描述符),是以就會看到0,1,2,3這個四個檔案了。但如果我們直接
ls /proc/self/fd/3
,這個時候
ls
的程序還沒有獲得3這個描述符,就嘗試去打開3這個不存在的檔案,是以就報錯了。在CentOS的文檔中提到了這個檔案夾的作用:
The /proc/self/ directory is a link to the currently running process. This allows a process to look at itself without having to know its process ID.
另外提一下bash程序中的255檔案描述符,這個是bash獨有的一個小“trick”,其對應的檔案是一個終端裝置:
這次碰到的問題抽象點說就是擷取資訊的手段本身會影響資訊,這樣的問題在很多地方都有展現,簡單的例如用
ps aux | wc
通過行數來擷取程序數,但
ps aux
本身在運作的時候就會形成一個程序,以後需要注意;)
參考:
- An introduction to bash completion
- What is the use of file descriptor 255 in bash process
- Coreutils - GNU core utilities