无法在MacOS上的`/ dev/fd`上调用`ioutil.ReadDir`

And*_*son 4 macos go

我试着运行以下Go代码:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    items, err := ioutil.ReadDir("/dev/fd")
    if err != nil {
        panic(err)
    }
    fmt.Println(items)
}
Run Code Online (Sandbox Code Playgroud)

我刚收到这个错误:

panic: lstat /dev/fd/4: bad file descriptor

goroutine 1 [running]:
main.main()
    /Users/andy/Desktop/demo.go:11 +0xe8
exit status 2
Run Code Online (Sandbox Code Playgroud)

/dev/fd文件夹的绝对存在,并且有一个/dev/fd/4里面就有我ls了.

$ ls -Al /dev/fd
total 9
crw--w----  1 andy  tty     16,   4 Jan 25 00:16 0
crw--w----  1 andy  tty     16,   4 Jan 25 00:16 1
crw--w----  1 andy  tty     16,   4 Jan 25 00:16 2
dr--r--r--  3 root  wheel      4419 Jan 23 20:42 3/
dr--r--r--  1 root  wheel         0 Jan 23 20:42 4/
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?为什么我不能读这个目录?我试图端口的ls命令去这里,所以我想能够在此目录中,以产生类似的输出读取ls.

编辑:我作为非root用户运行一切.可执行位/dev/fd 设置.

$ ls -al /dev | grep fd
dr-xr-xr-x   1 root  wheel                 0 Jan 23 20:42 fd/

$ stat /dev/fd/4 # same result with -L flag
stat: /dev/fd/4: stat: Bad file descriptor
Run Code Online (Sandbox Code Playgroud)

Art*_*Art 7

首先,让我们记住/ dev/fd很特别.它的内容对于每个进程都是不同的(因为它们反映了该进程的文件描述符),因此它并不意味着ls可以列出它的任何内容,因为它的内容对于ls您的程序而言是不同的.

无论如何,这是一个稍微更新的程序版本,而不是让ioutil背后的事情,我们自己做,看看发生了什么:

package main

import (
        "fmt"
        "time"
        "os"
        "log"
)

func main() {
        d, err := os.Open("/dev/fd")
        if err != nil {
                log.Fatal(err)
        }
        names, err := d.Readdirnames(0)
        if err != nil {
                log.Fatal(err)
        }
        for _, n := range names {
                n = "/dev/fd/" + n
                fmt.Printf("file: %s\n", n)
                _, err := os.Lstat(n)
                if err != nil {
                        fmt.Printf("lstat error: %s - %v\n", n, err)
                }
        }

        time.Sleep(time.Second * 200)
}
Run Code Online (Sandbox Code Playgroud)

然后在运行时它给了我:

file: /dev/fd/0
file: /dev/fd/1
file: /dev/fd/2
file: /dev/fd/3
file: /dev/fd/4
lstat error: /dev/fd/4 - lstat /dev/fd/4: bad file descriptor
Run Code Online (Sandbox Code Playgroud)

所以这确实是同样的问题.我在最后添加了睡眠,以便进程不会死亡,以便我们可以调试它具有哪些文件描述符.在另一个终端:

$ lsof -p 7861
COMMAND  PID USER   FD     TYPE     DEVICE SIZE/OFF       NODE NAME
foo     7861  art  cwd      DIR        1,4     2272     731702 /Users/art/src/go/src
foo     7861  art  txt      REG        1,4  1450576 8591078117 /private/var/folders/m7/d614cd9x61s0l3thb7cf3rkh0000gn/T/go-build268777304/command-line-arguments/_obj/exe/foo
foo     7861  art  txt      REG        1,4   837248 8590944844 /usr/lib/dyld
foo     7861  art    0u     CHR       16,4   0t8129        645 /dev/ttys004
foo     7861  art    1u     CHR       16,4   0t8129        645 /dev/ttys004
foo     7861  art    2u     CHR       16,4   0t8129        645 /dev/ttys004
foo     7861  art    3r     DIR 37,7153808        0        316 /dev/fd
foo     7861  art    4u  KQUEUE                                count=0, state=0x8
Run Code Online (Sandbox Code Playgroud)

我们可以看到fd 4是KQUEUE.Kqueue文件描述符用于管理OSX epoll上的事件,类似于Linux,如果您知道该机制.

似乎OSX不允许stat使用/dev/fd我们可以验证的kqueue文件描述符:

$ cat > foo.c
#include <sys/types.h>
#include <sys/event.h>
#include <sys/stat.h>
#include <stdio.h>
#include <err.h>

int
main(int argc, char **argv)
{
    int fd = kqueue();
    char path[16];
    struct stat st;

    snprintf(path, sizeof(path), "/dev/fd/%d", fd);
    if (lstat(path, &st) == -1)
        err(1, "lstat");
    return 0;
}
$ cc -o foo foo.c && ./foo
foo: lstat: Bad file descriptor
$
Run Code Online (Sandbox Code Playgroud)

OSX上的Go程序需要一个kqueue来处理各种事件(不完全确定哪些事件,但可能是信号,定时器和各种文件事件).

你有四个选择:不要统计,忽略错误,不要触摸/ dev/fd因为它很奇怪,说服Apple这是一个bug.