Как работают файловые дескрипторы?

Может кто-нибудь сказать мне, почему это не работает? Я играю с файловыми дескрипторами, но чувствую себя немного потерянным.

#!/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 или вы можете изменить метод выполнения скрипта и перенаправить эти дополнительные файловые дескрипторы куда угодно.

Другие вопросы по тегам