我想使用bash为编写额外的诊断消息打开一个新描述符。我不希望使用stderr,因为stderr应该只包含由bash调用的程序的输出。我也希望用户能够重定向我的自定义描述符。
我尝试了以下方法:
exec 3>/dev/tty
echo foo1
echo foo2 >&2
echo foo3 >&3
但是当我尝试重定向文件描述符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
但是当我尝试重定向文件描述符3时,输出仍然写入终端。
$ ./test.sh >/dev/null 2>/dev/null 3>/dev/null
foo3
首先父级 shell 将文件描述符 3 设置为 /dev/null
然后你的程序将文件描述符 3 设置为 /dev/tty
因此你遇到的症状并不令人惊讶。
编辑:你可以检查文件描述符 3 是否已经设置:
if [[ ! -e /proc/$$/fd/3 ]]
then
exec 3>/dev/tty
fi
-t
只能检测 fd 是否连接到终端,而不能检测其是否存在。+1 说明。 - Kelvinbash -c 'if [[ ! -e /dev/fd/3 ]]; then echo "fd3 non-existent"; fi'
。然而,在Solaris上它不起作用,不过我也不太关心。 - Kelvin简单来说:如果父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
)
exec 3<<<hello
的目的是什么;将其放在fdexists
测试之前会使测试每次都为真。 - Kelvinperl
部分是另一种解决方案。exec 3<<<hello
这里是为了测试。取消注释这行代码的效果与从外部重定向文件描述符相同。 - x-yuri更新
这是可以做到的。请参考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"
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>&-