在网上看到了这篇文章,真的时很不错,这是系统管理员的胜利。

简单的说,作者6个月前,在一个screen session里面起了一个很长很长的命令, 由于时间太久且HIST_SIZE变量不够大,这个命令已经在命令历史文件里面找不到了。同样的,6个月的命令行输出足够把最开始发送命令的那一行从screen的scroll back buffer里面顶出去。好像是在所有的地方都没有这条命令记录了一样,但是这条命令又非常重要,怎么办?

看过《少数派报告》电影的也许还记得,即使所有的地方都找不到记录了,当初产生这条记录的precrime还记得。在这里,命令最初是在哪个bash进程里执行的,这个进程还是知道的,即使HIST_FILE里已经没有了记录。那么怎么弄到这条命令记录呢?….

思路是去看bash的源代码,看看bash怎么处理历史记录的:

$ find . -iname \*.c | grep hist
./lib/readline/history.c
./lib/readline/examples/histexamp.c
./lib/readline/histexpand.c
./lib/readline/histsearch.c
./lib/readline/histfile.c
./bashhist.c

翻开histfile.c找到了这样几行

int write_history (filename)
     const char *filename;
{
  return (history_do_write (filename, history_length, HISTORY_OVERWRITE));
}

显然write_history这个函数有点用处….

$ gdb --pid 8909
...
Loaded symbols for /lib/i386-linux-gnu/i686/cmov/libnss_files.so.2
0xb76e7424 in __kernel_vsyscall ()

(gdb) call write_history("/tmp/foo")
$1 = 0

(gdb) detach

(gdb) q

$ tail -1 /tmp/foo
while true ; do echo 1 ; echo 2>/dev/null ; sleep 30 ; done

不错,这样就找回来那条运行的命令了。

不过慢着,如果…仅仅是如果,6个月以内对bash升过级,原本的/bin/bash文件已经被新版本替换,怎么办?

$ ssh IamInHell
[ttsiod@IamInHell] su -
....
[root@IAmInHell ~] gdb --pid 53165
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
...
Attaching to process 53165
/bin/bash (deleted): No such file or directory

不用担心,其实proc文件系统对每个运行的程序都有备份。

[root@IamInHell] lsof +L1 | grep bash | grep 53165
bash      53165   root  txt    REG  253,0   938832     0 791023 /bin/bash (deleted)

[root@IamInHell] cat /proc/53165/exe > /tmp/oldBash

于是你可以用下面的命令找回那条珍贵的历史记录了。

[root@IamInHell] gdb --pid 53165 /tmp/oldBash
...
Loaded symbols for /lib64/libnss_files.so.2
0x0000003d37aac8be in waitpid () from /lib64/libc.so.6
...
(gdb) call write_history("/tmp/foo")
$1 = 0
(gdb) detach
Detaching from program: /tmp/oldBash, process 53165
(gdb) q

[root@IamInHell] tail -1 /tmp/foo
while true ; do ...

或许我一辈子也用不上这样的技巧。