Может кто-нибудь сказать мне, почему это не работает? Я играю с файловыми дескрипторами, но чувствую себя немного потерянным.
Первые три строки работают нормально, но в двух последних произошла ошибка. Зачем?
Ответ 2
Это старый вопрос, но нужно одно уточнение.
В то время как ответы Carl Norum и dogbane верны, предполагается, что измените ваш script, чтобы он работал.
Я хотел бы отметить, что вам не нужно менять script:
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Он работает, если вы вызываете его по-другому:
./fdtest 3>&1 4>&1
что означает перенаправление файловых дескрипторов 3 и 4 на 1 (что является стандартным выходом).
Дело в том, что script отлично подходит для написания дескрипторов, отличных от 1 и 2 (stdout и stderr) , если эти дескрипторы предоставляются родительским процессом.
Ваш пример на самом деле довольно интересен, потому что этот script может записывать 4 разных файла:
./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
Теперь у вас есть выход в 4 отдельных файлах:
$ for f in file*; do echo $f:; cat $f; done
file1.txt:
This
file2.txt:
is
file3.txt:
a
file4.txt:
test.
Что является более интересным, так это то, что ваша программа не должна иметь права на запись для этих файлов, потому что она фактически не открывает их.
Например, когда я запускаю sudo -s
, чтобы изменить пользователя на root, создайте каталог с правами root и попробуйте запустить следующую команду в качестве обычного пользователя (rsp в моем случае), например:
# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt'
Я получаю сообщение об ошибке:
bash: file1.txt: Permission denied
Но, если я перенаправляю вне su
:
# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt
(обратите внимание на разницу в одинарных кавычках) работает, и я получаю:
# ls -alp
total 56
drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./
drwxrwxr-x 3 rsp rsp 4096 Jun 23 15:01 ../
-rw-r--r-- 1 root root 5 Jun 23 15:05 file1.txt
-rw-r--r-- 1 root root 39 Jun 23 15:05 file2.txt
-rw-r--r-- 1 root root 2 Jun 23 15:05 file3.txt
-rw-r--r-- 1 root root 6 Jun 23 15:05 file4.txt
которые являются 4 файлами, принадлежащими root, в каталоге, принадлежащем root - , хотя script не имеет разрешений для создания этих файлов.
Другим примером может быть использование chroot-тюрьмы или контейнера и запуск программы внутри, где у нее не будет доступа к этим файлам, даже если она была запущена как root и все же перенаправляет эти дескрипторы извне, где вам нужно, без фактического предоставления доступа к всю файловую систему или что-то еще в этом script.
Дело в том, что вы обнаружили очень интересный и полезный механизм. Вам не нужно открывать все файлы внутри вашего script, как было предложено в других ответах. Иногда бывает полезно перенаправить их во время вызова script.
Подводя итог:
echo "This"
фактически эквивалентен:
echo "This" >&1
и запустить программу как:
./program >file.txt
совпадает с:
./program 1>file.txt
Номер 1 - это только номер по умолчанию, и он является стандартным.
Но даже эта программа:
#!/bin/bash
echo "This"
может вызвать ошибку "Плохой дескриптор". Как? При запуске как:
./fdtest2 >&-
Выход будет:
./fdtest2: line 2: echo: write error: Bad file descriptor
Добавление >&-
(то же самое, что и 1>&-
) означает закрытие стандартного вывода. Добавление 2>&-
означает закрытие stderr.
Вы даже можете сделать более сложную вещь. Ваш оригинальный script:
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
при запуске просто:
./fdtest
печатает:
This
is
./fdtest: line 4: 3: Bad file descriptor
./fdtest: line 5: 4: Bad file descriptor
Но вы можете использовать дескрипторы 3 и 4, но номер 1 не работает:
./fdtest 3>&1 4>&1 1>&-
Он выводит:
./fdtest: line 2: echo: write error: Bad file descriptor
is
a
test.
Если вы хотите, чтобы дескрипторы с ошибками 1 и 2 вышли из строя, выполните следующие действия:
./fdtest 3>&1 4>&1 1>&- 2>&-
Вы получаете:
a
test.
Почему? Ничего не случилось? Он сделал, но без stderr (дескриптор файла номер 2) вы не видели сообщения об ошибках!
Я думаю, что очень полезно поэкспериментировать таким образом, чтобы понять, как работают дескрипторы и их перенаправление.
Ваш script действительно интересный пример - и я утверждаю, что он не сломан вообще, вы просто использовали его неправильно!:)
Ответ 4
Добавить к ответу от rsp и ответить на вопрос в комментариях к этому ответу от @MattClimbs.
Вы можете проверить, открыт файловый дескриптор или нет, попытавшись перенаправить его на него раньше, и, если это не удастся, откройте желаемый нумерованный файловый дескриптор, например, /dev/null
. Я делаю это регулярно в сценариях и использую дополнительные файловые дескрипторы для передачи дополнительных деталей или ответов после return #
.
script.sh
#!/bin/bash
2>/dev/null >&3 || exec 3>/dev/null
2>/dev/null >&4 || exec 4>/dev/null
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Stderr перенаправляется в /dev/null
чтобы отбросить возможный bash: #: Bad file descriptor
ответ bash: #: Bad file descriptor
и ||
используется для обработки следующей команды exeС#>/dev/null
когда предыдущая выходит с ненулевым статусом. Если дескриптор файла уже открыт, два теста вернут нулевой статус и команда exec...
не будет выполнена.
Вызов скрипта без перенаправлений дает:
# ./script.sh
This
is
В этом случае перенаправления для a
и test
отправляются в /dev/null
Вызов скрипта с определенным перенаправлением приводит к:
# ./script.sh 3>temp.txt 4>>temp.txt
This
is
# cat temp.txt
a
test.
Первое перенаправление 3>temp.txt
перезаписывает файл temp.txt
а 4>>temp.txt
добавляет к файлу.
В конце вы можете определить файлы по умолчанию для перенаправления внутри скрипта, если вы хотите что-то отличное от /dev/null
или вы можете изменить метод выполнения скрипта и перенаправить эти дополнительные файловые дескрипторы куда угодно.