在 bash 中,如何打开可外部重定向的可写文件描述符?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10587857/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In bash, how do I open a writable file descriptor that's externally redirectable?
提问by Kelvin
I'm trying to use bash to open a new descriptor for writing extra diagnostic messages. I don't want to use stderr, because stderr should only contain output from the programs called by bash. I also want my custom descriptor to be redirectable by the user.
我正在尝试使用 bash 打开一个新的描述符来编写额外的诊断消息。我不想使用 stderr,因为 stderr 应该只包含 bash 调用的程序的输出。我还希望我的自定义描述符可由用户重定向。
I tried this:
我试过这个:
exec 3>/dev/tty
echo foo1
echo foo2 >&2
echo foo3 >&3
But when I try to redirect fd 3, the output still writes to the terminal.
但是当我尝试重定向 fd 3 时,输出仍然写入终端。
$ ./test.sh >/dev/null 2>/dev/null 3>/dev/null
foo3
采纳答案by kaluy
Simple enough: If the parent shell is notredirecting fd 3, then test.shwill be redirecting fd 3 to /dev/tty.
很简单:如果父 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
回答by cdarke
First the parent shell sets file descriptor 3 to /dev/null
Then your program sets file descriptor 3 to /dev/tty
So your symptoms are not really surprising.
Edit: You could check to see if fd 3 has been set:
首先父 shell 将文件描述符 3 设置为 /dev/null
然后你的程序将文件描述符 3 设置为 /dev/tty
所以你的症状并不奇怪。
编辑:您可以检查是否已设置 fd 3:
if [[ ! -e /proc/$$/fd/3 ]]
then
exec 3>/dev/tty
fi
回答by gregc
Here's a way to check if a file descriptor has already been set using the (Bash) shell only.
这是一种仅使用(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
)
回答by gregc
@Kelvin: Here's your modified script you asked for (plus some tests).
@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>&-
回答by Kelvin
Update
更新
This can be done. See kaluy's answer for the simplest way.
这是可以做到的。有关最简单的方法,请参阅 kaluy 的答案。
Original Answer
原答案
It seems the answer is "you can't". Any descriptors created in a script don't apply to the shell which called the script.
答案似乎是“你不能”。在脚本中创建的任何描述符都不适用于调用该脚本的 shell。
I figured out how to do it using ruby though, if anyone is interested. See also the update using perl.
如果有人感兴趣,我想出了如何使用 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"
Note that this obviously won't work in a daemon - it's not connected to a terminal.
请注意,这显然不适用于守护进程——它没有连接到终端。
UPDATE
更新
If ruby isn't your thing, you can simply call a bash script from the ruby script. You'll need the open4 gem/library for reliable piping of output:
如果 ruby 不是你的东西,你可以简单地从 ruby 脚本中调用一个 bash 脚本。您将需要 open4 gem/library 来实现可靠的输出管道:
require 'open4'
# ... insert begin/rescue/end block from above
Open4.spawn('./out.sh', :out => out)
UPDATE 2
更新 2
Here's a way using a bit of perl and mostly bash. You must make sure perl is working properly on your system, because a missing perl executable will also return a non-zero exit code.
这是一种使用一点 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

