Проверка правильности дескриптора файла

12

Я бы хотел, чтобы скрипт bash выводил дополнительную информацию в файловые дескрипторы (FD), большие или равные 3, когда они открыты. Чтобы проверить, открыт ли FD, я разработал следующий трюк:

if (printf '' 1>&3) 2>&-; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi

Этого достаточно для моих нужд, но мне любопытно, есть ли более идиоматический способ проверки, действителен ли FD. Меня особенно интересует, существует ли отображение fcntl(1)системного вызова на команду оболочки, которая позволила бы извлекать флаги FD ( O_WRONLYи O_RDWRпроверять, является ли FD доступным для записи, и O_RDONLYи O_RDWRпроверять, является ли FD читаемым).

Witiko
источник

Ответы:

12

В ksh(варианты AT & T и pdksh) или zsh, вы можете сделать:

if print -nu3; then
  echo fd 3 is writeable
fi

Они не будут ничего писать на этом fd, но все же проверят, доступен ли для записи fd (использует fcntl(3, F_GETFL)) и сообщают об ошибке в противном случае

$ ksh -c 'print -nu3' 3< /dev/null
ksh: print: -u: 3: fd not open for writing

(который вы можете перенаправить на /dev/null)

С помощью bash, я думаю, ваш единственный вариант - проверить, dup()успешно ли, как в вашем подходе, хотя это не гарантирует, что fd доступен для записи (или вызвать внешнюю утилиту ( zsh/ perl...) для этого fcntl()).

Обратите внимание, что в bash(как и большинство оболочек), если вы используете (...)вместо этого {...;}, это будет форкировать дополнительный процесс. Ты можешь использовать:

if { true >&3; } 2<> /dev/null

вместо этого, чтобы избежать форка (за исключением оболочки Bourne, где перенаправление составных команд всегда вызывает подоболочку). Не используйте :вместо trueэтого специальную встроенную функцию , так как это приведет к закрытию оболочки, когда bash находится в режиме соответствия POSIX.

Однако вы можете сократить его до:

if { >&3; } 2<> /dev/null
Стефан Шазелас
источник
@mikeserve, re: ваше редактирование, с чем это связано <>? Оболочка не будет читать из своего stderr, почему вы хотите открыть ее в режиме чтения + записи? Что вы имеете в виду с тем, что произошло с внутренним? ?
Стефан
7

В описании использования приложения POSIX вы найдете следующее:command

Есть некоторые преимущества в подавлении особых характеристик специальных встроенных элементов в некоторых случаях. Например:

command exec > unwritable-file

не приводит к прерыванию неинтерактивного сценария, поэтому сценарий может проверить состояние вывода.

Вот почему вы можете просто сделать:

if    command >&3
then  echo 3 is open >&3
else  ! echo 3 is not open
fi    2<>/dev/null

Или...

{ command >&3
  printf %s\\n%.0d  string "0$(($??8:0))" >&"$(($??1:3))"
} 2<>/dev/null

Который будет записывать строку, за которой следует электронная \nстрока, либо в stdout, либо в 3, и все равно передавать ненулевой статус выхода, когда 3 не открыта, потому что математика, выполненная в $?результате, не в состоянии преобразовать восьмеричный 08 в десятичный%, но обрезает до нуля вообще восьмеричное 00 .

Или...

command exec >&3 || handle_it

Но если вы используете ksh93, вы можете просто сделать:

fds

Для списка открытых файловых дескрипторов. Добавьте, -lчтобы увидеть, куда они идут.

mikeserv
источник
3

Дескрипторы открытых файлов можно найти в /proc/<pid>/fd. Например, перечислить дескрипторы открытых файлов текущей оболочки, ls -l /proc/$$/fdкоторые должны дать вам что-то вроде:

total 0
lrwx------ 1 testuser testuser 64 jun  1 09:11 0 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 1 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:11 2 -> /dev/pts/3
lrwx------ 1 testuser testuser 64 jun  1 09:39 255 -> /dev/pts/3

Когда вы открываете файл с помощью:

touch /tmp/myfile
exec 7</tmp/myfile

Должен быть указан новый ls -l /proc/$$/fd:

lr-x------ 1 testuser testuser 64 jun  1 09:11 7 -> /tmp/myfile

Если вы закроете файловый дескриптор снова, exec 7>&-его тоже не будет в списке /proc/$$/fd.

Ламберт
источник
2
Все это довольно специфично для Linux. FWIW.
lcd047
1
Протестировал его как на Linux, так и на Solaris (10 и 11). Разница в том, что вам нужно использовать, pfiles <pid>чтобы увидеть, какой файловый дескриптор подключен к какому файлу при ls -lотображении соединения в Linux.
Ламберт
Мне нравится компактность [ -e /proc/$$/fd/3 ], но я предпочитаю не полагаться на procfs, поскольку она устарела во FreeBSD и, возможно, в других un * unes.
Witiko
1
Приводит меня к альтернативе использования pfiles <pid>или lsof -p <pid>посмотреть, какие файловые дескрипторы открыты.
Ламберт
1
/procне существует вообще на OpenBSD. В FreeBSD и NetBSD он должен быть mountявно задан и /proc/<PID>не иметь подкаталога fd.
lcd047
3

Ваш трюк выглядит мило; но для идиоматического способа мне интересно, почему вы не использовали:

if ( exec 1>&3 ) 2>&-
Janis
источник
Это действительно более чистый путь.
Витико
5
Это создает подоболочку, хотя большинство оболочек означает разветвление процесса. Это не гарантирует, что fd доступен для записи. Вы можете использовать, { true >&3; } 2> /dev/nullчтобы избежать вилки. Или { command exec >&3; } 2> /dev/nullесли вы хотите перенаправить стандартный вывод на него.
Стефан Шазелас
@Stephane; Уловка подоболочки, изобретенная @Witiko, заключалась в том, чтобы не влиять на файловые дескрипторы текущей среды при использовании перенаправления для получения перенаправления. - Не могли бы вы рассказать о «записываемом fd», о котором вы упомянули?
Янис
2
{ true >&3; } 2> /dev/nullтакже не повлияет на текущую среду и не будет разветвляться (кроме как в оболочке Bourne). Я имею в виду, что (exec 1>&3) 2>&-вернет истину для открытого файла в режиме только для чтения.
Стефан Шазелас
1
execспециальная встроенная функция выйдет из оболочки в случае ее сбоя (для bash, только в режиме соответствия POSIX). command execпредотвращает это. trueне является специальным встроенным. Обратите внимание , что execи command execдействительно влияют на текущую среду (вот почему я сказал , если вы хотите , чтобы перенаправить стандартный вывод на него ).
Стефан Шазелас
-1

Если вы заинтересованы в решении с низким уровнем разветвления, чтобы использовать его повторно, я бы предложил эту функцию:

checkfd () {
    exec 2> / dev / null
    если exec> & 3; тогда
        exec 1> / dev / tty
        echo "fd3 OK"
    еще
        эхо "fd3 KO"
    фи
    exec 2> / dev / tty
}

И вот что он производит с zsh:

$ checkfd            
FD3 KO
$ checkfd 3> / dev / null
fd3 ОК
$
Дан
источник
В большинстве оболочек exec >&3будет убивать оболочку, когда 3 не открыт.
mikeserv
По крайней мере, это работает над zshи bash. Не могли бы вы предоставить оболочку , на которой неисправный execвызвала exit?
дан
Да. В bashделать set -o posixи попробуйте еще раз. В zsh... я думаю, что это вопрос установки переменной env POSIX_BUILTINSв ненулевое значение - но я забываю не случайно. В любом случае, zshэто не оболочка, которая пытается соответствовать POSIX, и поэтому она определенно нестандартна. Обе эти оболочки избегают совместимости за то, что некоторые считают удобством.
mikeserv
Он также работает на простой оболочке Bourne.
Дан
В bash set -o posixпопытка удалась.
дан
-1

Это кажется очень простым (см. Комментарии):

[ -r /proc/$$/fd/$FD ] && echo "File descriptor $FD is readable"
[ -w /proc/$$/fd/$FD ] && echo "File descriptor $FD is writable"

В качестве дополнительного ... Тест [-r file] не указывает, действительно ли какие-либо данные ожидают чтения (/ dev / null проходит этот тест (см. Комментарии)).

[ -r /proc/$$/fd/4 ] \
  && [ read -t 0.0001 -N 0 <&4 ] \
  && echo "Data is waiting to be read from file descriptor 4"

Требуется некоторое небольшое число для аргумента тайм-аута (read -t), или могут быть пропущены данные, которые требуют некоторого вычисления. Требуется читаемый тест ([-r файл]) или команда чтения будет бомбить, если файл не читается. Это на самом деле не будет читать какие-либо данные, потому что число байтов равно нулю (чтение -N 0).

Павел
источник
если вы собираетесь использовать систему Linux, вы также можете взглянуть на нее /proc/<pid>/fdinfo/<fd>, в которой перечислены все режимы открытых файлов, flags:см. здесь . Почему ваша вторая часть (даже после исправления явной ошибки): read -t .1 -N0 <&4не скажет, есть ли данные для чтения на fd 4: просто попробуйте 4</dev/null.
Мосви
И, конечно же, [ -r /proc/$$/fd/$FD ]не сообщается, является ли дескриптор файла $FDдоступным для чтения, но если файл, из которого он был открыт, можно открыть снова , с другим дескриптором файла, для чтения:exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy
-1

Вопрос довольно старый - но так или иначе - почему бы просто не использовать встроенные функции?

for i in {0..5} ; do if [ -t $i ]; then echo "$i is a valid FD"; else echo "$i is INVALID FD"; fi; done

Выход:

0 is a valid FD
1 is a valid FD
2 is a valid FD
3 is INVALID FD
4 is INVALID FD
5 is INVALID FD

Итак, чтобы ответить на вопрос - предлагаю:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi
Dimas
источник
-tне проверяет, является ли дескриптор файла допустимым, но подключен ли он к tty. echo yup |Добавьте к вашему сценарию a , и он скажет, что 0 is INVALID FD, хотя на самом деле это очень правильный fd, труба.
Мосви