【问题标题】:Bash read/write file descriptors -- seek to start of fileBash 读/写文件描述符——寻找文件的开头
【发布时间】:2010-10-01 10:23:49
【问题描述】:

我尝试在bash中使用读/写文件描述符,以便我可以删除文件描述符之后引用的文件,例如:

F=$(mktemp)
exec 3<> "$F"
rm -f "$F"

echo "Hello world" >&3
cat <&3

但是cat 命令没有输出。如果我使用单独的文件描述符进行读写,我可以实现我想要的:

F=$(mktemp)
exec 3> "$F"
exec 4< "$F"
rm -f "$F"

echo "Hello world" >&3
cat <&4

打印Hello world

我怀疑当你从写入切换到读取时,bash 不会自动寻找文件描述符的开头,以下 bash 和 python 代码的组合证实了这一点:

fdrw.sh

exec 3<> tmp
rm tmp

echo "Hello world" >&3
exec python fdrw.py

fdrw.py

import os  

f = os.fdopen(3)
print f.tell()
print f.read()

给出:

$ bash fdrw.sh
12

$ # This is the prompt reappearing

有没有办法只使用 bash 来实现我想要的?

【问题讨论】:

  • 为什么要在读/写之前删除文件?
  • 在 Unix 中,当您删除一个文件时,该文件实际上并没有被删除,直到它的所有打开的文件描述符都被关闭。因此,在打开临时文件后立即删除是一种常见做法,因为它保证没有其他进程可以恶意更改文件,并且在您的进程关闭文件或退出后关闭文件。
  • 你为什么不喜欢你自己的拥有独立读写描述符的方法呢?这似乎是最简单的方法。

标签: linux bash unix file-io read-write


【解决方案1】:

我找到了一种在 bash 中执行此操作的方法,但它依赖于 exec &lt; /dev/stdin 的一个不起眼的功能,它实际上可以根据 http://linux-ip.net/misc/madlug/shell-tips/tip-1.txt 倒回 stdin 的文件描述符:

F=$(mktemp)
exec 3<> "$F"
rm -f "$F"

echo "Hello world" >&3
{ exec < /dev/stdin; cat; } <&3

写入描述符不受此影响,因此您仍然可以在 cat 之前将输出附加到描述符 3。

遗憾的是,即使使用最新的 bash 版本,我也只能在 Linux 下而不是在 MacOS (BSD) 下工作。所以它看起来不太便携。

【讨论】:

    【解决方案2】:

    如果您碰巧想查找 bash 文件描述符,则可以使用子进程,因为它继承了父进程的文件描述符。这是一个执行此操作的示例 C 程序。

    seekfd.c

    #define _FILE_OFFSET_BITS 64
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int main(int argc, char* argv[])
    {
        /* Arguments: fd [offset [whence]]
         * where
         * fd: file descriptor to seek
         * offset: number of bytes from position specified in whence
         * whence: one of
         *  SEEK_SET (==0): from start of file
         *  SEEK_CUR (==1): from current position
         *  SEEK_END (==2): from end of file
         */
        int fd;
        long long scan_offset = 0;
        off_t offset = 0;
        int whence = SEEK_SET;
        int errsv; int rv;
        if (argc == 1) {
            fprintf(stderr, "usage: seekfd fd [offset [whence]]\n");
            exit(1);
        }
        if (argc >= 2) {
            if (sscanf(argv[1], "%d", &fd) == EOF) {
                errsv = errno;
                fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
                exit(1);
            }
        }
        if (argc >= 3) {
            rv = sscanf(argv[2], "%lld", &scan_offset);
            if (rv == EOF) {
                errsv = errno;
                fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
                exit(1);
            }
            offset = (off_t) scan_offset;
        }
        if (argc >= 4) {
            if (sscanf(argv[3], "%d", &whence) == EOF) {
                errsv = errno;
                fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
                exit(1);
            }
        }
    
        if (lseek(fd, offset, whence) == (off_t) -1) {
            errsv = errno;
            fprintf(stderr, "%s: %s\n", argv[0], strerror(errsv));
            exit(2);
        }
    
        return 0;
    }
    

    【讨论】:

    • 我确信会有 aperl oneliner 可以做同样的事情并且更便携
    【解决方案3】:

    没有。 bash 的重定向没有任何“寻求”的概念。它在一个长流中从头到尾读取/写入(大部分)。

    【讨论】:

    • 那么基本上,在 bash 中读/写描述符的唯一原因是将它们传递给执行的进程?
    • 为了提供更多的通道,而不仅仅是标准输入、标准输出和 sterr,是的。
    • 它也常用于网络连接... exec {w}&lt;&gt;/dev/tcp/www.google.com/80 并且您可以使用它来写入文件的中间部分。您以读/写方式打开它,读取 N 个字符,然后写入它。在antofthy.gitlab.io/info/shell/file_handles.txt 中搜索“读写”以获取示例。
    【解决方案4】:

    尝试更改命令顺序:

    F=$(mktemp tmp.XXXXXX)
    exec 3<> "$F"
    echo "Hello world" > "$F"
    rm -f "$F"
    
    #echo "Hello world" >&3
    cat <&3
    

    【讨论】:

    • @Dennis 这个解决方案确实有效。猫没有从明显删除的文件中读取。它正在从仍然打开的描述符中读取。即使最后一个(硬)链接已被删除,您仍然可以使用该描述符访问文件的内容。
    • 此解决方案的问题是echo 可能需要很长时间,这意味着临时文件会在文件系统上停留很长时间。如果这是可以接受的,那么您可以简单地使用文件名而不是 fd 3 进行重定向。
    【解决方案5】:

    当您像这样在 bash 中打开文件描述符时,它可以作为 /dev/fd/ 中的文件访问。 在那你可以做cat,它会从头开始读取,或者附加(echo "something" &gt;&gt; /dev/fd/3),它会把它添加到最后。 至少在我的系统上它的行为方式是这样的。 (另一方面,我似乎无法让“cat

    【讨论】:

      【解决方案6】:
      #!/bin/bash
      F=$(mktemp tmp.XXXXXX)
      exec 3<> $F
      rm $F
      
      echo "Hello world" >&3
      cat /dev/fd/3
      

      As suggested 在其他答案中,cat 会在读取之前为您倒回文件描述符,因为它认为它只是一个普通文件。

      【讨论】:

        【解决方案7】:

        要“倒带”文件描述符,您可以简单地使用/proc/self/fd/3

        测试脚本:

        #!/bin/bash
        
        # Fill data
        FILE=test
        date +%FT%T >$FILE
        
        # Open the file descriptor and delete the file
        exec 5<>$FILE
        rm -rf $FILE
        
        # Check state of the file
        # should return an error as the file has been deleted
        file $FILE
        
        # Check that you still can do multiple reads or additions
        for i in {0..5}; do
            echo ----- $i -----
        
            echo . >>/proc/self/fd/5
            cat /proc/self/fd/5
        
            echo
            sleep 1
        done
        

        尝试在脚本运行时将其杀死 -9,您会看到与使用陷阱方法发生的情况相反,文件实际上已被删除。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-02-05
          • 1970-01-01
          • 1970-01-01
          • 2011-06-05
          • 2023-03-17
          • 2011-01-31
          • 1970-01-01
          相关资源
          最近更新 更多