ls proc $$ self fd 3 255 引發的一些瑣事


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

說明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本身在運行的時候就會形成一個進程,以后需要注意;)

參考:

  1. An introduction to bash completion
  2. What is the use of file descriptor 255 in bash process
  3. Coreutils - GNU core utilities


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM