在bash中,如何打开可从外部重定向的可写文件描述符?

时间:2012-05-14 17:01:20

标签: bash file-descriptor io-redirection

我正在尝试使用bash打开一个新的描述符来编写额外的诊断消息。我不想使用stderr,因为stderr应该只包含bash调用的程序的输出。我还希望我的自定义描述符可由用户重定向。

我试过了:

exec 3>/dev/tty
echo foo1
echo foo2 >&2
echo foo3 >&3

但是当我尝试重定向fd 3时,输出仍然写入终端。

$ ./test.sh >/dev/null 2>/dev/null 3>/dev/null
foo3

5 个答案:

答案 0 :(得分:7)

首先,父shell将文件描述符3设置为/ dev / null
然后您的程序将文件描述符3设置为/ dev / tty
所以你的症状并不令人惊讶。

编辑:您可以查看是否已设置fd 3:

if [[ ! -e /proc/$$/fd/3 ]]
then
    exec 3>/dev/tty
fi

答案 1 :(得分:5)

足够简单:如果父shell 重定向fd 3,则test.sh会将fd 3重定向到/dev/tty

if ! { exec 0>&3; } 1>/dev/null 2>&1; then
   exec 3>/dev/tty
fi
echo foo1
echo foo2 >&2
echo foo3 >&3

答案 2 :(得分:2)

这是一种检查文件描述符是否仅使用(Bash)shell设置的方法。

(
# cf. "How to check if file descriptor exists?", 
# http://www.linuxmisc.com/12-unix-shell/b451b17da3906edb.htm

exec 3<<<hello

# open file descriptors get inherited by child processes, 
# so we can use a subshell to test for existence of fd 3
(exec 0>&3) 1>/dev/null 2>&1 && 
    { echo bash: fd exists; fdexists=true; } || 
    { echo bash: fd does NOT exists; fdexists=false; }

perl -e 'open(TMPOUT, ">&3") or die' 1>/dev/null 2>&1 && 
    echo perl: fd exists || echo perl: fd does NOT exist

${fdexists} && cat <&3
)

答案 3 :(得分:1)

<强>更新

这可以做到。以最简单的方式看kaluy的答案。

原始答案

似乎答案是“你不能”。脚本中创建的任何描述符都不适用于调用脚本的shell。

如果有兴趣的话,我想出了如何使用ruby来做到这一点。另请参阅使用perl进行更新。

begin
  out = IO.new(3, 'w')
rescue Errno::EBADF, ArgumentError
  out = File.open('/dev/tty', 'w')
end
p out.fileno
out.puts "hello world"

请注意,这显然不适用于守护程序 - 它没有连接到终端。

更新

如果ruby不是你的东西,你可以简单地从ruby脚本中调用bash脚本。你需要open4 gem / library来获得可靠的输出管道:

require 'open4'

# ... insert begin/rescue/end block from above

Open4.spawn('./out.sh', :out => out)

更新2

这是一种使用perl和bash的方法。您必须确保perl在您的系统上正常工作,因为丢失的perl可执行文件也将返回非零退出代码。

perl -e 'open(TMPOUT, ">&3") or die' 2>/dev/null
if [[ $? != 0 ]]; then
  echo "fd 3 wasn't open"
  exec 3>/dev/tty
else
  echo "fd 3 was open"
fi
echo foo1
echo foo2 >&2
echo foo3 >&3

答案 4 :(得分:1)

@Kelvin:这是您要求修改的脚本(加上一些测试)。

echo '
#!/bin/bash

# If test.sh is redirecting fd 3 to somewhere, fd 3 gets redirected to /dev/null;
# otherwise fd 3 gets redirected to /dev/tty.
#{ exec 0>&3; } 1>/dev/null 2>&1 && exec 3>&- || exec 3>/dev/tty
{ exec 0>&3; } 1>/dev/null 2>&1 && exec 3>/dev/null || exec 3>/dev/tty

echo foo1
echo foo2 >&2
echo foo3 >&3

' > test.sh

chmod +x test.sh

./test.sh
./test.sh 1>/dev/null
./test.sh 2>/dev/null
./test.sh 3>/dev/null
./test.sh 1>/dev/null 2>/dev/null
./test.sh 1>/dev/null 2>/dev/null 3>&-
./test.sh 1>/dev/null 2>/dev/null 3>/dev/null
./test.sh 1>/dev/null 2>/dev/null 3>/dev/tty

# fd 3 is opened for reading the Here String 'hello'
# test.sh should see that fd 3 has already been set by the environment
# man bash | less -Ip 'here string'
exec 3<<<hello
cat <&3
# If fd 3 is not explicitly closed, test.sh will determine fd 3 to be set.
#exec 3>&- 
./test.sh

exec 3<<<hello
./test.sh 3>&-