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
本身在运行的时候就会形成一个进程,以后需要注意;)
参考:
- An introduction to bash completion
- What is the use of file descriptor 255 in bash process
- Coreutils - GNU core utilities
随机推荐
- Servlet中web.xml 以及 <url-pattern>总结
web.xml中添加Servlet配置信息 使用Eclipse创建Servlet,会自动的在WEB-INF下的web.xml中声明,但是有的时候需要我们手动的写入配置信息,以下就是Servlet在we ...
- Web Mining and Big Data 公开课学习笔记 ---lecture0
0.1 课程主要内容:Big data technologies , Machine Learning and AI 0.6 OUTLINE: predict the future using ...
- react入门到进阶(三)
一.react样式 1.内联样式 在以前写html+css的时候,引入css的时候有一种方法就是内联,而在react中又有些不一样,样式是用变量的形式,如下 const styleComponentH ...
- web更改AD用户密码
web更改AD用户密码 #web更改AD密码 #网站配置 绑定域名ad.test.cn 功能,更改AD用户密码 #参考http://bbs.51cto.com/thread-1379675-1.htm ...
- 这一次带你彻底了解Cookie
前言 网络早期最大的问题之一是如何管理状态.简而言之,服务器无法知道两个请求是否来自同一个浏览器.当时最简单的方法是在请求时,在页面中插入一些参数,并在下一个请求中传回参数.这需要使用包含参数的隐藏的 ...
- codeforces 895A Pizza Separation 枚举
codeforces 895A Pizza Separation 题目大意: 分成两大部分,使得这两部分的差值最小(注意是圆形,首尾相连) 思路: 分割出来的部分是连续的,开二倍枚举. 注意不要看成0 ...
- CCF-201409-2-画图
问题描述 试题编号: 201409-2 试题名称: 画图 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 在一个定义了直角坐标系的纸上,画一个(x1,y1)到(x2,y2)的矩 ...
- Tp3.2 和 Tp5.0之间的区别
5.0版本和之前版本的差异较大,本篇对熟悉3.2版本的用户给出了一些5.0的主要区别. URL和路由 5.0的URL访问不再支持普通URL模式,路由也不支持正则路由定义,而是全部改为规则路由配合变量规 ...
- Akka(43): Http:SSE-Server Sent Event - 服务端主推消息
因为我了解Akka-http的主要目的不是为了有关Web-Server的编程,而是想实现一套系统集成的api,所以也需要考虑由服务端主动向客户端发送指令的应用场景.比如一个零售店管理平台的服务端在完成 ...
- 【NOIP2014提高组】寻找道路
https://www.luogu.org/problem/show?pid=2296 满足条件的路径:路径上的所有点的出边所指向的点都与终点连通.反过来,不满足条件的路径:路径上至少一点的出边所指向 ...