Bash 读/写文件描述符——寻找文件的开始

问题描述 投票:0回答:9

我尝试在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 来实现我想要的?

linux bash unix file-io read-write
9个回答
12
投票

我找到了一种在 bash 中执行此操作的方法,但它依赖于

exec < /dev/stdin
的一个模糊功能,它实际上可以根据 http://linux-ip.net/misc/madlug/shell- 倒回标准输入的文件描述符-提示/提示-1.txt:

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

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

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

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


10
投票

如果你碰巧想要查找 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;
}

6
投票

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


6
投票

当你像这样在 bash 中打开文件描述符时,它就可以作为文件在

/dev/fd/
中访问。 在那上面你可以做
cat
,它会从头开始读取,或者追加(
echo "something" >> /dev/fd/3
),它会把它添加到最后。 至少在我的系统上它是这样的。 (另一方面,我似乎无法获得“猫<&3" to work, even if I don't do any writing to the descriptor).


5
投票

尝试改变命令的顺序:

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

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

3
投票
#!/bin/bash
F=$(mktemp tmp.XXXXXX)
exec 3<> $F
rm $F

echo "Hello world" >&3
cat /dev/fd/3

As suggested in other answer,

cat
会在读取之前为您倒带文件描述符,因为它认为它只是一个普通文件。


1
投票

要“倒回”文件描述符,您可以简单地使用

/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

尝试在脚本运行时kill -9,你会发现与trap方法相反,文件实际上被删除了。


0
投票

@sanmai 对答案的扩展...

并确认正在发生的事情......

#/bin/bash
F=$(mktemp tmp.XXXXXX)
exec 3<>$F     # open the temporary file for read and write
rm $F          # delete file, though it remains on file system

echo "Hello world!" >&3    # Add a line to a file
cat /dev/fd/3              # Read the whole file
echo "Bye" >>/dev/fd/3     # Append another line
cat /dev/fd/3              # Read the whole file
echo "Goodbye" >&3         # Overwrite second line
cat /dev/fd/3              # Read the whole file

cat <&3                    # Try to Rewind (no output)
echo "Cruel World!" >&3    # Still adds a line on end
cat /dev/fd/3              # Read the whole file

shell_seek 3 6 0           # seek fd 3 to position 6
echo -n "Earth" >&3        # Overwrite 'World'
shell_seek 3               # rewind fd 3
cat <&3                    # Read the whole file put 3 at end

请注意,

echo Goodbye
会覆盖第二行,因为文件描述符 &3 没有被猫更改!

所以我尝试使用

cat <&3
没有输出任何东西,可能是因为文件描述符在文件末尾。查看它是否倒回给定的描述符。它没有。

最后一部分是使用提供、编译和命名的“C”程序

shell_seek
是的,它似乎可以工作,因为第一个“世界”被“地球”取代,倒带(寻求开始)工作允许最后一只猫再次读取整个文件。它会再次将 fd 放在文件的末尾!

使用 perl 而不是 C 来做它也不是那么难。 例如

perl -e 'open(FD,">&3"); seek(FD,0,0);'
将文件描述符 3 倒回到文件的开头。

我现在已经制作了

shell_seek
的perl版本,所以我不必一直为不同的系统重新编译它。不仅如此,脚本还可以“告诉”您当前的文件描述符偏移量,还可以“截断”文件描述符指向的文件。这两种操作在使用 seek 时都很常用,因此包含这些函数似乎是个好主意。您可以从...下载脚本 https://antofthy.gitlab.io/software/#shell_seek


0
投票

`mkfifo tubeA 执行 3<> 管 A rm管A

echo "Hello world" >&3 猫<&3`

+c

© www.soinside.com 2019 - 2024. All rights reserved.