Как работают файловые дескрипторы?
Может кто-нибудь сказать мне, почему это не работает? Я играю с файловыми дескрипторами, но чувствую себя немного потерянным.
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Первые три строки работают нормально, но в двух последних произошла ошибка. Зачем?
3 ответа
Файловые дескрипторы 0, 1 и 2 предназначены для stdin, stdout и stderr соответственно.
Файловые дескрипторы 3, 4, .. 9 предназначены для дополнительных файлов. Для того, чтобы использовать их, вы должны сначала открыть их. Например:
exec 3<> /tmp/foo #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.
Для получения дополнительной информации обратитесь к Руководству по расширенному написанию сценариев: Глава 20. Перенаправление ввода / вывода.
Это старый вопрос, но одна вещь требует уточнения.
Хотя ответы Carl Norum и dogbane верны, предполагается, что сценарий нужно изменить, чтобы он работал.
Я хотел бы отметить, что вам не нужно менять скрипт:
#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4
Это работает, если вы вызываете это по-другому:
./fdtest 3>&1 4>&1
что означает перенаправление файловых дескрипторов 3 и 4 на 1 (что является стандартным выводом).
Дело в том, что скрипт прекрасно подходит для записи в дескрипторы, отличные от 1 и 2 (stdout и stderr), если эти дескрипторы предоставляются родительским процессом.
Ваш пример на самом деле довольно интересен, потому что этот скрипт может записывать в 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, хотя у сценария не было разрешений на создание этих файлов.
Другим примером может быть использование chroot jail или контейнера и запуск программы внутри, где у нее не будет доступа к этим файлам, даже если она запускается от имени root и все еще перенаправляет эти дескрипторы извне, где вам нужно, фактически не предоставляя доступ ко всему файлу. Система или что-нибудь еще к этому сценарию.
Дело в том, что вы обнаружили очень интересный и полезный механизм. Вам не нужно открывать все файлы внутри вашего скрипта, как было предложено в других ответах. Иногда полезно перенаправить их во время вызова скрипта.
Подводя итог, это:
echo "This"
на самом деле эквивалентно:
echo "This" >&1
и запустить программу как:
./program >file.txt
такой же как:
./program 1>file.txt
Число 1 является просто номером по умолчанию, и это стандартный вывод.
Но даже эта программа:
#!/bin/bash
echo "This"
может выдать ошибку "Bad descriptor". Как? Когда запускается как:
./fdtest2 >&-
Выход будет:
./fdtest2: line 2: echo: write error: Bad file descriptor
Добавление >&-
(что так же, как 1>&-
) означает закрытие стандартного вывода. Добавление 2>&-
будет означать закрытие stderr.
Вы даже можете сделать более сложную вещь. Ваш оригинальный сценарий:
#!/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) вы не увидели сообщений об ошибках!
Я думаю, что очень полезно экспериментировать таким образом, чтобы понять, как работают дескрипторы и их перенаправление.
Ваш сценарий действительно очень интересный пример - и я утверждаю, что он вообще не сломан, вы просто неправильно его использовали!:)
Это терпит неудачу, потому что те файловые дескрипторы не указывают ни на что! Обычные файловые дескрипторы по умолчанию являются стандартным вводом 0
стандартный вывод 1
и стандартный поток ошибок 2
, Поскольку ваш скрипт не открывает никаких других файлов, других допустимых файловых дескрипторов нет. Вы можете открыть файл в Bash, используя exec
, Вот модификация вашего примера:
#!/bin/bash
exec 3> out1 # open file 'out1' for writing, assign to fd 3
exec 4> out2 # open file 'out2' for writing, assign to fd 4
echo "This" # output to fd 1 (stdout)
echo "is" >&2 # output to fd 2 (stderr)
echo "a" >&3 # output to fd 3
echo "test." >&4 # output to fd 4
А теперь запустим это:
$ ls
script
$ ./script
This
is
$ ls
out1 out2 script
$ cat out*
a
test.
$
Как видите, дополнительный вывод был отправлен на запрошенные файлы.
Добавить к ответу от 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
ответ и ||
используется для обработки следующей команды exec #>/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
или вы можете изменить метод выполнения скрипта и перенаправить эти дополнительные файловые дескрипторы куда угодно.