Как работают файловые дескрипторы? - PullRequest
66 голосов
/ 16 августа 2011

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

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

Первые три строки работают нормально, но последние две ошибки вышли.Почему?

Ответы [ 4 ]

97 голосов
/ 16 августа 2011

Файловые дескрипторы 0, 1 и 2 предназначены для stdin, stdout и stderr соответственно.

Файловые дескрипторы 3, 4, .. 9 предназначены для дополнительных файлов. Для того, чтобы использовать их, вам нужно сначала открыть их. Например:

exec 3<> /tmp/foo  #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.

Подробнее см. В Расширенное руководство по написанию сценариев Bash: Глава 20. Перенаправление ввода / вывода .

56 голосов
/ 23 июня 2016

Это старый вопрос, но одна вещь требует уточнения .

Хотя ответы Карла Норума и Догбена верны, предполагается, что измените сценарий, чтобы он работал .

Я хотел бы отметить, что вам не нужно менять скрипт :

#!/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.

Что еще интереснее 1032 *, так это то, что вашей программе не нужно иметь права на запись для этих файлов, потому что она фактически не открывает их.

Например, когда я запускаю 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-тюрьмы или контейнера и запуск программы внутри, где у нее не будет доступа к этим файлам, даже если она запускается от имени 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) вы не видели сообщений об ошибках!

Я думаю, что очень полезно экспериментировать таким образом, чтобы понять, как работают дескрипторы и их перенаправление.

Ваш скрипт действительно очень интересный пример - и я утверждаю, что он вообще не сломан, вы просто неправильно его использовали! :)

18 голосов
/ 16 августа 2011

Сбой, потому что эти файловые дескрипторы ни на что не указывают!Обычные файловые дескрипторы по умолчанию: стандартный ввод 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.
$

Как видите, дополнительный вывод был отправлен в запрошенные файлы.

0 голосов
/ 03 мая 2019

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

...