Ответ 1
Достаточно просто: если родительская оболочка не перенаправляет 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, чтобы открыть новый дескриптор для записи дополнительных диагностических сообщений. Я не хочу использовать 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
Достаточно просто: если родительская оболочка не перенаправляет 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
Сначала родительская оболочка устанавливает дескриптор файла 3 в /dev/null
Затем ваша программа устанавливает дескриптор файла 3 в /dev/tty
Итак, ваши симптомы не удивительно.
Edit: вы можете проверить, установлен ли fd 3:
if [[ ! -e /proc/$$/fd/3 ]]
then
exec 3>/dev/tty
fi
Здесь можно проверить, установлен ли файловый дескриптор только с помощью оболочки (Bash).
(
# 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 для простейшего способа.
Оригинальный ответ
Кажется, ответ "вы не можете". Любые дескрипторы, созданные в script, не применяются к оболочке, которая называется script.
Я понял, как это сделать, используя 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 - это не ваша вещь, вы можете просто вызвать bash script из ruby script. Вам понадобится библиотека 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: Здесь вы указали свой измененный script (плюс несколько тестов).
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>&-