說明Music文件夾下有這三個文件,我也就不需要提前用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
本身在運行的時候就會形成一個進程,以后需要注意;)
參考: