什么时候使用附加的文件描述符?


74

我知道您可以创建文件描述符并将输出重定向到它。例如

exec 3<> /tmp/foo # open fd 3.
echo a >&3 # write to it
exec 3>&- # close fd 3.

但是您可以在没有文件描述符的情况下执行相同的操作:

FILE=/tmp/foo
echo a > "$FILE"

我正在寻找一个很好的示例,说明何时需要使用附加的文件描述符。

Answers:


50

大多数命令具有一个输入通道(标准输入,文件描述符0)和一个输出通道(标准输出,文件描述符1),或者对它们自己打开的几个文件进行操作(因此您传递了一个文件名)。(这是标准错误(fd 2)的补充,该错误通常会一直过滤到用户。)但是有时使用一条命令充当来自多个源或多个目标的过滤器的命令有时会很方便。例如,这是一个简单的脚本,它将文件中的奇数行与偶数行分开

while IFS= read -r line; do
  printf '%s\n' "$line"
  if IFS= read -r line; then printf '%s\n' "$line" >&3; fi
done >odd.txt 3>even.txt

现在,假设您要对奇数行和偶数行应用不同的过滤器(但不要将它们放回去,那将是一个不同的问题,通常对于shell来说是不可行的)。在外壳程序中,您只能将命令的标准输出通过管道传递给另一个命令。要通过管道传输另一个文件描述符,您需要先将其重定向到fd 1。

{ while  done | odd-filter >filtered-odd.txt; } 3>&1 | even-filter >filtered-even.txt

另一个更简单的用例是过滤命令的错误输出

exec M>&N将脚本描述符的其余部分重定向到另一个文件描述符(或直到另一个这样的命令再次更改了文件描述符)。exec M>&N和之间的功能有些重叠somecommand M>&N。该exec表单更强大,因为它不必嵌套:

exec 8<&0 9>&1
exec >output12
command1
exec <input23
command2
exec >&9
command3
exec <&8

其他可能感兴趣的示例:

还有更多示例:

PS:这是一个令人惊讶的问题,来自使用fd 3重定向的网站上最受好评的文章的作者!


我宁愿说“大多数命令具有单输出或双输出通道 -stdout(fd 1)和很多时候stderr(fd 2)”。
rozcietrzewiacz 2011年

另外,能否顺便解释一下为什么使用while IFS= read -r line;?我认为,IFS在这里无效,因为您仅将值分配给一个变量(line)。看到这个问题。
rozcietrzewiacz 2011年

@rozcietrzewiacz我提到了stderr,并且看到了答案的第一部分,IFS即使您将a读入单个变量(仍保留前导空白),为何也会有所作为。
吉尔斯

你不能这样做sed -ne 'w odd.txt' -e 'n;w even.txt'吗?
通配符

1
@Wildcard当然可以使用其他工具进行相同的操作。但是,此答案的目的是说明shell中的重定向。
吉尔斯

13

这是使用额外的FD作为bash脚本聊天控制的示例:

#!/bin/bash

log() {
    echo $* >&3
}
info() {
    echo $* >&4
}
err() {
    echo $* >&2
}
debug() {
    echo $* >&5
}

VERBOSE=1

while [[ $# -gt 0 ]]; do
    ARG=$1
    shift
    case $ARG in
        "-vv")
            VERBOSE=3
        ;;
        "-v")
            VERBOSE=2
        ;;
        "-q")
            VERBOSE=0
        ;;
        # More flags
        *)
        echo -n
        # Linear args
        ;;
    esac
done

for i in 1 2 3; do
    fd=$(expr 2 + $i)
    if [[ $VERBOSE -ge $i ]]; then
        eval "exec $fd>&1"
    else
        eval "exec $fd> /dev/null"
    fi
done

err "This will _always_ show up."
log "This is normally displayed, but can be prevented with -q"
info "This will only show up if -v is passed"
debug "This will show up for -vv"

8

在命名管道(fifos)的上下文中,使用其他文件描述符可以启用非阻塞管道行为。

(
rm -f fifo
mkfifo fifo
exec 3<fifo   # open fifo for reading
trap "exit" 1 2 3 15
exec cat fifo | nl
) &
bpid=$!

(
exec 3>fifo  # open fifo for writing
trap "exit" 1 2 3 15
while true;
do
    echo "blah" > fifo
done
)
#kill -TERM $bpid

请参阅:在脚本中过早关闭命名管道?


1
你挖了我一个老问题:)乍得是对的,你会遇到比赛的情况。
n0pe

6

当您想在变量中捕获stdout但仍要写到屏幕时(例如在bash脚本用户界面中),额外的文件描述符非常有用。

arg1 string to echo 
arg2 flag 0,1 print or not print to 3rd fd stdout descriptor   
function ecko3 {  
if [ "$2" -eq 1 ]; then 
    exec 3>$(tty) 
    echo -en "$1" | tee >(cat - >&3)
    exec 3>&- 
else 
    echo -en "$1"  
fi 
}

2
我知道这不是一个新的答案,但是我不得不凝视这一段时间,以查看它的作用,并认为如果有人添加了一个使用此函数的示例会有所帮助。一个命令-df,在这种情况下。dl.dropboxusercontent.com/u/54584985/mytest_redirect

3

这是使用附加文件描述符似乎合适的另一种情况(在Bash中):

命令行参数的Shell脚本密码安全性

env -i bash --norc   # clean up environment
set +o history
read -s -p "Enter your password: " passwd
exec 3<<<"$passwd"
mycommand <&3  # cat /dev/stdin in mycommand

1

示例:使用flock强制脚本以文件锁顺序运行

一个示例是利用文件锁定来强制脚本在系统范围内串行运行。如果您不希望两个相同类型的脚本在同一文件上运行,这将很有用。否则,这两个脚本将相互干扰,并可能破坏数据。

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#open file descriptor 3 for writing
exec 3> /tmp/file.lock

#create an exclusive lock on the file using file descriptor 3
#exit if lock could not be obtained
flock -n 3

#execute serial code

#remove the file while the lock is still obtained
rm -f /tmp/file.lock

#close the open file handle which releases the file lock and disk space
exec 3>&-

通过定义锁定和解锁功能使用羊群

您也可以将此锁定/解锁逻辑包装到可重用的函数中。脚本退出时(错误或成功),以下trap内置的shell将自动释放文件锁定。 trap帮助清理文件锁。该路径/tmp/file.lock应为硬编码路径,以便多个脚本可以尝试将其锁定。

# obtain a file lock and automatically unlock it when the script exits
function lock() {
  exec 3> /tmp/file.lock
  flock -n 3 && trap unlock EXIT
}

# release the file lock so another program can obtain the lock
function unlock() {
  # only delete if the file descriptor 3 is open
  if { >&3 ; } &> /dev/null; then
    rm -f /tmp/file.lock
  fi
  #close the file handle which releases the file lock
  exec 3>&-
}

unlock上面的逻辑是在释放锁定之前删除文件。这样,它将清除锁定文件。由于该文件已删除,因此该程序的另一个实例能够获得文件锁定。

在脚本中使用锁定和解锁功能

您可以像下面的示例一样在脚本中使用它。

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#try to lock (else exit because of non-zero exit code)
lock

#system-wide serial locked code

unlock

#non-serial code

如果您希望代码等待直到能够锁定,则可以调整脚本,如下所示:

set -e

#wait for lock to be successfully obtained
while ! lock 2> /dev/null; do
  sleep .1
done

#system-wide serial locked code

unlock

#non-serial code

0

作为一个具体的例子,我只是编写了一个脚本,该脚本需要来自子命令的时序信息。使用额外的文件描述符使我能够捕获time命令的stderr,而不会中断子命令的stdout或stderr。

(time ls -9 2>&3) 3>&2 2> time.txt

这样做是将lsstderr指向fd 3,将fd 3指向脚本的stderr,并将timestderr指向文件。运行脚本时,其stdout和stderr与子命令相同,可以照常重定向。仅time的输出重定向到文件。

$ echo '(time ls my-example-script.sh missing-file 2>&3) 3>&2 2> time.txt' > my-example-script.sh
$ chmod +x my-example-script.sh 
$ ./my-example-script.sh 
ls: missing-file: No such file or directory
my-example-script.sh
$ ./my-example-script.sh > /dev/null
ls: missing-file: No such file or directory
$ ./my-example-script.sh 2> /dev/null
my-example-script.sh
$ cat time.txt

real    0m0.002s
user    0m0.001s
sys 0m0.001s
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.