我正在尝试使用 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
我正在尝试使用 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
首先父 shell 将文件描述符 3 设置为 /dev/null
然后你的程序将文件描述符 3 设置为 /dev/tty
所以你的症状并不令人惊讶。
编辑:您可以检查是否已设置 fd 3:
if [[ ! -e /proc/$$/fd/3 ]]
then
exec 3>/dev/tty
fi
很简单:如果父 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
这是一种检查文件描述符是否已仅使用 (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
)
更新
这是可以做到的。有关最简单的方法,请参见 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
@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>&-