最近才狠命的折腾数码日购入的一个 DS920+ NAS,在上面用计划任务来同步两个目录的文件,似乎是在登陆它的 SSH 后的 rsync 同步命令与放在计划任务里的执行效果不一样。于是想看看计划任务里的 rsync -av --delete folder1 folder
时的控制台输出。到哪里去找呢?登陆到 SSH 于只能用 ps
命令看到它的进程 ID
yanbin@nas:~$ ps -ef|grep rsync
SynoRsy+ 10270 1 0 Sep28 ? 00:00:00 /usr/bin/rsync --daemon
root 14067 14066 51 23:03 ? 00:00:02 rsync -av folder1/ folder2/
root 14076 14067 0 23:03 ? 00:00:00 rsync -av folder1/ folder2/
root 14077 14076 58 23:03 ? 00:00:02 rsync -av folder1/ folder2/
ls -l /proc/<pid>/fd
列出进程所有打开的文件描述符
yanbin@nas:~$ sudo ls -l /proc/14067/fd
total 0
lrwx------ 1 root root 64 Sep 29 23:04 0 -> /dev/null
l-wx------ 1 root root 64 Sep 29 23:04 1 -> /volume1/@tmp/synoscheduler/logs/@14057/output.log
l-wx------ 1 root root 64 Sep 29 23:04 2 -> /volume1/@tmp/synoscheduler/logs/@14057/output.log
l-wx------ 1 root root 64 Sep 29 23:04 3 -> /var/log/rsync_signal.error
lr-x------+ 1 root root 64 Sep 29 23:04 4 -> /volume1/folder1/files/2021-04-11_05-00-01_NG2NHJET02.tar.gz
l-wx------ 1 root root 64 Sep 29 23:04 5 -> 'pipe:[15890693]'
lr-x------ 1 root root 64 Sep 29 23:04 6 -> 'pipe:[15890694]'
1 ->
和 2 ->
才有指向实际的文件,否则它们也都是 /dev/null
从上面的文件描述符列表也可以想见像服务端程序如 Web 服务或数据库服务的 /proc/<pid>/fd
一定会是个很大的列表,实际上也确实是,本站的 MySQL 服务进程 ls -l /proc/862/fd | wc -l
显示的是 344。
文件描述符的 0, 1 和 2 分别是标准输入(stdin), 标准输出(stdout) 和标准错误输出(stderr),其他的为非标准的。
有了 1 -> /volume1/@tmp/synoscheduler/logs/@14057/output.log
, 我们就知道标准输出被重定向到了这个文件中去了,所以查看前面 rsync
后台任务的实时输出就能够用下面的命令
$ tail -f /volume1/@tmp/synoscheduler/logs/@14057/output.log
执行当中有什么问题也就一目了然了。
问题的延伸
知道了往哪里找标准输入输出的源头与目的地后,其他的一些有关输入输出重定向的问题就好理解了,像下面的那些
- nohup CMD &
- CMD < file
- &>file, 2>&1, 1>&2, 2>&1 > /dev/null
- 像编程语言中的输入输出重定向,如 Python 的 sys.stdout = open('log.txt', 'w')
欲查看它们最终的输入输出是什么都可以通过查看文件描述符的方式找到
$ sudo ls -l /proc/<pid>/fd
接下来看几个例子,将要执行下方的 Python 代码
test.py
1 2 3 4 5 6 |
from datetime import datetime import time while True: print(datetime.now(), flush=True) time.sleep(2) |
nohup CMD &
# nohup python3 test.py &
# nohup python3 test.py&
[2] 14023
nohup: ignoring input and appending output to 'nohup.out'
# ls -l /proc/14023/fd
total 0
l-wx------ 1 root root 64 Sep 29 23:33 0 -> /dev/null
l-wx------ 1 root root 64 Sep 29 23:33 1 -> /root/nohup.out
l-wx------ 1 root root 64 Sep 29 23:33 2 -> /root/nohup.out
其实 nohup 就是 nohup python3 test.py > nohup.out &
的省略形式, 所以需要重定向到别的文件只需
# nohup python3 test.py > log.txt &
查看它的文件描述符将会是
# ls -l /proc/14029/fd
total 0
l-wx------ 1 root root 64 Sep 29 23:37 0 -> /dev/null
l-wx------ 1 root root 64 Sep 29 23:37 1 -> /root/log.txt
l-wx------ 1 root root 64 Sep 29 23:37 2 -> /root/log.txt
当重定输出到文件中时,标准错误输出也会进到同一个文件中去,所以和下面的命令是一样的
# nohup python3 test.py 1>&2 > log.txt &
# nohup python3 test.py 2>12 > log.txt &
注:nohup 命令的功能这里主要是当终端断开后,用 nohup 放置到后台的进程仍然被运行的 -- 原来是这么认为的,其实 nohup 还兼具输出重定向的功能。
>file 2>&1
学习到这里,发现先前看到的 1>&2 还是 2>&1 好像是多余的。再试了下以下几种情况
# nohup python3 test.py > /dev/null & ---------- 有 nohup 时标准输出与标准错误输出都为 /dev/null
[2] 14041
# nohup: ignoring input and redirecting stderr to stdout
# ls -l /proc/14041/fd
total 0
l-wx------ 1 root root 64 Sep 29 23:48 0 -> /dev/null
l-wx------ 1 root root 64 Sep 29 23:48 1 -> /dev/null
l-wx------ 1 root root 64 Sep 29 23:48 2 -> /dev/null
#
# python3 test.py > /dev/null & ---------- 没有 nohup 时标准错误输出为 /dev/pts/0, 与加了 nohup 不同
[3] 14043
# ls -l /proc/14043/fd
total 0
lrwx------ 1 root root 64 Sep 29 23:49 0 -> /dev/pts/0
l-wx------ 1 root root 64 Sep 29 23:49 1 -> /dev/null
lrwx------ 1 root root 64 Sep 29 23:49 2 -> /dev/pts/0
#
# python3 test.py > /dev/null 2>&1& ---- /dev/null 写在 2&1 前面才能把标准输出与标准错误输出都定向到 /dev/null
[5] 14100
# ls -l /proc/14100/fd
total 0
lrwx------ 1 root root 64 Sep 29 23:50 0 -> /dev/pts/0
l-wx------ 1 root root 64 Sep 29 23:50 1 -> /dev/null
l-wx------ 1 root root 64 Sep 29 23:50 2 -> /dev/null
# python3 test.py 2>&1 > /dev/null & ------------ 2>&1 写在 /dev/null 之前没有效果
[7] 14103
# ls -l /proc/14103/fd
total 0
lrwx------ 1 root root 64 Sep 29 23:51 0 -> /dev/pts/0
l-wx------ 1 root root 64 Sep 29 23:51 1 -> /dev/null
lrwx------ 1 root root 64 Sep 29 23:51 2 -> /dev/pts/0
编程中的重定向
测试一个 Python 中的 sys.stdout 重定向到文件中的例子,把前面的 Python 代码改为如下
1 2 3 4 5 6 7 8 9 |
from datetime import datetime import time import sys sys.stdout = open('log.txt', 'w') while True: print(datetime.now(), flush=True) time.sleep(2) |
再来执行,然后看文件描述符
# nohup python3 test.py &
[1] 14150
# nohup: ignoring input and appending output to 'nohup.out'
# ls -l /proc/14150/fd
total 0
l-wx------ 1 root root 64 Sep 30 00:02 0 -> /dev/null
l-wx------ 1 root root 64 Sep 30 00:02 1 -> /root/nohup.out
l-wx------ 1 root root 64 Sep 30 00:02 2 -> /root/nohup.out
l-wx------ 1 root root 64 Sep 30 00:02 3 -> /root/log.txt
#
# python3 test.py &
[3] 14157
#
# ls -l /proc/14157/fd
total 0
lrwx------ 1 root root 64 Sep 30 00:05 0 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 30 00:05 1 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 30 00:05 2 -> /dev/pts/0
l-wx------ 1 root root 64 Sep 30 00:05 3 -> /root/log.txt
Python 创建了一个新的文件描述符编号 3
, 查看 /root/nohup.out
和 /root/log.txt
, 实际只有在 /root/log.txt
中才能看到输出。不知道用 Java 语言的 System.setOut() 是不是一样的效果。
标准输入的重定向
快速看一下输入重定向的情形
# python3 test.py < input.txt &
[5] 14167
root@nas-ubuntu:~# ls -l /proc/14167/fd
total 0
lr-x------ 1 root root 64 Sep 30 00:12 0 -> /root/input.txt
lrwx------ 1 root root 64 Sep 30 00:12 1 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 30 00:12 2 -> /dev/pts/0
改变的是标准输入文件描述符 0
指向到 /root/input.txt
文件,这还是好理解。
最后的最后
最后一句话就是 Linux 进程的数据从哪里来往哪里去,只要找到进程 ID, 用 ls -l /proc/<pid>/fd
查看它打开所有的文件描述符就行。
网络程序也不例外,比如一个 Docker 服务进程 dockerd
$ ps -ef|grep dockerd
root 667 1 0 Sep29 ? 00:00:01 /usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375
root 14178 1321 0 00:18 pts/1 00:00:00 grep --color=auto dockerd
$ ls -l /proc/667/fd
total 0
lr-x------ 1 root root 64 Sep 29 22:09 0 -> /dev/null
lrwx------ 1 root root 64 Sep 29 22:09 1 -> 'socket:[23694]'
lrwx------ 1 root root 64 Sep 29 22:09 10 -> 'socket:[23207]'
lrwx------ 1 root root 64 Sep 29 22:09 11 -> 'socket:[24401]'
lrwx------ 1 root root 64 Sep 29 22:09 12 -> /var/lib/docker/volumes/metadata.db
lr-x------ 1 root root 64 Sep 29 22:09 13 -> 'net:[4026531992]'
lrwx------ 1 root root 64 Sep 29 22:09 14 -> 'socket:[23275]'
lrwx------ 1 root root 64 Sep 29 22:09 15 -> 'socket:[23276]'
lrwx------ 1 root root 64 Sep 29 22:09 16 -> 'socket:[23277]'
lrwx------ 1 root root 64 Sep 29 22:09 17 -> 'socket:[23382]'
lrwx------ 1 root root 64 Sep 29 22:09 18 -> /var/lib/docker/buildkit/containerdmeta.db
lrwx------ 1 root root 64 Sep 29 22:09 19 -> /var/lib/docker/buildkit/snapshots.db
lrwx------ 1 root root 64 Sep 29 22:09 2 -> 'socket:[23694]'
lrwx------ 1 root root 64 Sep 29 22:09 20 -> /var/lib/docker/buildkit/metadata_v2.db
lrwx------ 1 root root 64 Sep 29 22:09 21 -> /var/lib/docker/buildkit/cache.db
lrwx------ 1 root root 64 Sep 29 22:09 3 -> 'socket:[19921]'
lrwx------ 1 root root 64 Sep 29 22:09 4 -> 'socket:[24195]'
lrwx------ 1 root root 64 Sep 29 22:09 5 -> 'anon_inode:[eventpoll]'
lrwx------ 1 root root 64 Sep 29 22:09 6 -> 'socket:[24196]'
lrwx------ 1 root root 64 Sep 29 22:09 7 -> 'socket:[24206]'
lrwx------ 1 root root 64 Sep 29 22:09 8 -> 'socket:[24374]'
lrwx------ 1 root root 64 Sep 29 22:09 9 -> 'socket:[23206]'
从中摘出 0, 1, 2 来
0 -> /dev/null
1 -> 'socket:[23694]
2 -> 'socket:[23694]
Docker 是一个 C/S 应用,确切说 docker 服务端提供的是 HTTP 服务,所以它的标准输出和标准错误输出定向到了一个网络连接。
曾经也研究过 Linux 下输入输出重定向的问题, Linux 输入输出重定向, &>file, 2>&1, 1>&2 等, 但当时并未从文件描述符方面去理解,因此理解的也没那么深。
本文链接 https://yanbin.blog/find-linux-backend-process-output/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。