2

如果你想用 Bash 覆盖一个文件,这很容易

echo "Hello world" > hosts

这似乎不适用于文件描述符

$ exec 3<> hosts

$ echo "Hello world" >&3
$ cat hosts
Hello world

$ echo "Hello world" >&3
$ cat hosts
Hello world
Hello world
4

1 回答 1

5

这是正确的。当 shell 调用open(2). 当您DUP2使用 FD(任何语言)时,打开文件时设置的标志在打开的 FD之间共享。在您的情况下,O_TRUNC只能在实际打开文件时指定。

<file要知道的重要一点是,模式和各种标志仅在使用、>file或类似命令打开文件时才确定。复制带有&修饰符的 FD 实质上会创建一个指向原始 FD 的“别名”,并保留与原始 FD 相同的所有状态。截断文件需要重新打开它。

如果您想轻松使用文件描述符,这是我的调试功能:

lsfd() {
    local ofd=${ofd:-2} target=${target:-$BASHPID}

    while [[ $1 == -* ]]; do
        if [[ -z $2 || $2 == *[![:digit:]]* ]]; then
            cat
            return 1
        fi
        case ${1##+(-)} in
            u)
                shift
                ofd=$1
                shift
                ;;
            t)
                shift
                target=$1
                shift
                ;;
            h|\?|help)
                cat
                return
        esac
    done <<EOF
USAGE: ${FUNCNAME} [-h|-?|--help] [-u <fd>] [ -t <PID> ] [<fd1> <fd2> <fd3>...]

This is a small lsof wrapper which displays the open
file descriptors of the current BASHPID. If no FDs are given,
the default FDs to display are {0..20}. ofd can also be set in the
environment.

    -u <fd>: Use fd for output. Defaults to stderr. Overrides ofd set in the environment.
    -t <PID>: Use PID instead of BASHPID. Overrides "target" set in the environment.
EOF

    IFS=, local -a 'fds=('"${*:-{0..20\}}"')' 'fds=("${fds[*]}")'
    lsof -a -p $target -d "$fds" +f g -- >&${ofd}
}

我喜欢不关闭标准输入,因为这有时会导致问题,所以首先保存它。

 $ ( { lsfd 3; cat <&3; } {savefd}<&0 <<<'hi' 3>&0- <&"${savefd}" )
COMMAND PID  USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
bash    920 ormaaj   3r   REG        LG   0,22        3 59975426 /tmp/sh-thd-8305926351 (deleted)
hi

如您所见,即使使用3>&0-运算符将​​ FD 0 移动到 3,文件仍保持打开状态O_RDONLY。复制描述符的选择>or<是任意的,并且仅在省略运算符左侧的 FD 时才用于确定默认值。

如果您确实想打开一个新的独立 FD,可以这样做:

$ ( {
cat <&4 >/dev/null; lsfd 3 4; echo there >&4; cat </dev/fd/3
} {savefd}<&0 <<<'hi' 3>&0- 4<>/dev/fd/3 <&"${savefd}"
)
COMMAND  PID  USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
bash    2410 ormaaj   3r   REG        LG   0,22        3 59996561 /tmp/sh-thd-8305914274 (deleted)
bash    2410 ormaaj   4u   REG     RW,LG   0,22        3 59996561 /tmp/sh-thd-8305914274 (deleted)
hi
there

现在 FD 4 真正“重新打开”到指向 FD 3 的文件,而不仅仅是复制(即使该文件已经被unlink(2)'d,如上所述)。首先打开 FD 4 并使用 搜索到末尾cat,然后将附加行写入文件。同时 FD 3 的查找位置仍然在开头,所以整个生成的文件可以cated 到终端。

同样的原则也适用于你的情况。

$ { echo 'Hello world'; echo 'hi' >/dev/fd/1; } >hosts; cat hosts
hi

或者可能更好的是使用两个单独的命令打开和关闭文件两次,而不使用exec.

除非绝对必要,否则我宁愿避免exec仅使用打开文件描述符。您必须记住明确关闭文件。如果您改用命令分组,文件将自动关闭,实际上给它们一个“范围”。这在原理上类似于 Python 的with声明。这可能避免了一些混乱。

另见

于 2012-06-17T04:32:33.360 回答