Bash,使用文件描述符覆盖?

时间:2012-06-17 03:37:40

标签: bash file-descriptor io-redirection

如果你想用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

1 个答案:

答案 0 :(得分:5)

这是对的。 shell调用open(2)时确定打开文件的模式。当您DUP2 FD(使用任何语言)时,打开文件时设置的标志为shared between open FDs。在您的情况下,O_TRUNC只能在文件实际打开时指定。

重要的是要知道只有在使用<file>file或类似文件打开文件时才会确定模式和各种标志。使用&修饰符复制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}
}

我喜欢不关闭stdin,因为这有时会导致问题,所以它会先被保存。

 $ ( { 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><的选择对于复制描述符是任意的,只有在省略运算符左侧的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的搜索位置仍处于开头,因此整个结果文件可以被设置为终端。

同样的原则可适用于您的案件。

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

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

除非绝对必要,否则我宁愿避免使用exec来打开文件描述符。您必须记住明确关闭该文件。如果使用命令分组,文件将自动关闭,实际上为它们提供了“范围”。这与Python的with语句原则上类似。这可能会避免一些混乱。

另见