Testando se um Descritor de Arquivo é Válido

12

Eu gostaria que um script bash produzisse informações adicionais para os descritores de arquivo (FDs) maiores ou iguais a 3, quando abertos. Para testar se um FD está aberto, criei o seguinte truque:

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

Isso é suficiente para minhas necessidades, mas estou curioso para saber se existe uma maneira mais idiomática de testar se um FD é válido. Estou especialmente interessado em saber se existe um mapeamento do fcntl(1)syscall para um comando shell, o que permitiria a recuperação de sinalizadores de FD ( O_WRONLYe O_RDWRpara testar se o FD é gravável O_RDONLYe O_RDWRpara testar se o FD é legível).

Witiko
fonte

Respostas:

12

Em ksh(variantes da AT&T e pdksh) ou zsh, você pode:

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

Eles não escreverão nada nesse fd, mas ainda verificarão se o fd é gravável (usando fcntl(3, F_GETFL)) e reportarão um erro caso contrário:

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

(para o qual você pode redirecionar /dev/null).

Com bash, acho que sua única opção é verificar se o dup()êxito é semelhante à sua abordagem, embora isso não garanta que o fd seja gravável (ou chame um utilitário externo ( zsh/ perl...) para fazer isso fcntl()).

Observe que em bash(como a maioria das conchas), se você usar em (...)vez de {...;}, isso irá bifurcar um processo extra. Você pode usar:

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

em vez disso, para evitar a bifurcação (exceto no shell Bourne, onde o redirecionamento de comandos compostos sempre causa um subshell). Não use em :vez disso, truepois ele é um built-in especial , pois isso causaria a saída do shell quando o bash estiver no modo de conformidade com POSIX.

No entanto, você pode reduzi-lo para:

if { >&3; } 2<> /dev/null
Stéphane Chazelas
fonte
@ mikeserve, re: sua edição, o que é isso <>? O shell não vai ler de seu stderr, por que você deseja abri-lo em leitura + gravação? O que você quer dizer com o que aconteceu com o intrínseco? ?
Stéphane Chazelas
7

Na descrição de uso do aplicativo POSIX, você encontrará o seguinte:command

Existem algumas vantagens em suprimir as características especiais de embutidos especiais de vez em quando. Por exemplo:

command exec > unwritable-file

não causa a interrupção de um script não interativo, para que o status da saída possa ser verificado pelo script.

É por isso que você pode simplesmente fazer:

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

Ou...

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

Que escreverá a sequência seguida por um \newline para stdout ou 3 e ainda transmitirá um status de saída diferente de zero quando 3 não estiver aberto, porque as contas feitas $?acabam falhando em converter o octal 08 para % decimal, mas não são absolutamente truncadas o octal 00 .

Ou...

command exec >&3 || handle_it

Mas se você estiver usando ksh93, basta:

fds

Para uma lista de descritores de arquivos abertos. Adicione -lpara ver para onde eles vão.

mikeserv
fonte
3

Os descritores de arquivos abertos podem ser encontrados em /proc/<pid>/fd. Para listar, por exemplo, os descritores de arquivo aberto do shell atual que você pode emitir, o ls -l /proc/$$/fdque deve fornecer algo como:

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

Quando você abre um arquivo usando:

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

Deve ser listado por um novo ls -l /proc/$$/fd:

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

Se você fechar o descritor de arquivo novamente, exec 7>&-ele também não estará listado /proc/$$/fd.

Lambert
fonte
2
Tudo isso é bastante específico para o Linux. FWIW.
Lcd047
1
Testei no Linux e também no Solaris (10 e 11). A diferença é que você precisa usar pfiles <pid>para ver qual descritor de arquivo está conectado a qual arquivo enquanto ls -lexibe a conexão no Linux.
Lambert
Eu gosto da compactação de [ -e /proc/$$/fd/3 ], mas prefiro não confiar nos procfs, pois ele foi descontinuado no FreeBSD e possivelmente em outros setores também.
Witiko
1
Leva-me à alternativa de usar pfiles <pid>ou lsof -p <pid>ver quais descritores de arquivo estão abertos.
Lambert
1
/procnão existe no OpenBSD. No FreeBSD e NetBSD, ele deve ser mountexplicado explicitamente e /proc/<PID>não possui um subdiretório fd.
Lcd047
3

Seu truque parece fofo; mas de uma maneira idiomática, eu me pergunto por que você não usou:

if ( exec 1>&3 ) 2>&-
Janis
fonte
Esta é, de fato, uma maneira mais limpa.
Witiko
5
Isso cria um subshell, embora a maioria dos shells signifique bifurcar um processo. Isso não garante que o fd seja gravável. Você pode usar { true >&3; } 2> /dev/nullpara evitar o garfo. Ou { command exec >&3; } 2> /dev/nullse você deseja redirecionar o stdout para ele.
Stéphane Chazelas
@Stephane; O truque do subshell que o @Witiko inventou foi não afetar os descritores de arquivo do ambiente atual ao usar um redirecionamento para obter um redirecionamento. - Você poderia elaborar o "cd gravável" que você mencionou?
Janis
2
{ true >&3; } 2> /dev/nulltambém não afetará o ambiente atual e não será bifurcado (exceto no shell Bourne). Quero dizer que (exec 1>&3) 2>&-retornará true para um fd aberto no modo somente leitura.
Stéphane Chazelas
1
execsendo um builtin especial sairá do shell se falhar (para o bash, somente quando estiver no modo de conformidade com POSIX). command execimpede isso. truenão é um builtin especial. Note-se que exece command execafetam o ambiente atual (é por isso que eu disse , se você deseja redirecionar stdout a ele ).
Stéphane Chazelas
-1

Se você estiver interessado em uma solução de baixo garfo para usá-la repetidamente, sugiro esta função:

checkfd () {
    exec 2> / dev / null
    se exec> & 3; então
        exec 1> / dev / tty
        eco "fd3 OK"
    outro
        eco "fd3 KO"
    fi
    exec 2> / dev / tty
}

E aqui está o que ele produz com um zsh:

$ checkfd            
fd3 KO
$ checkfd 3> / dev / null
fd3 OK
$
dan
fonte
Na maioria das conchas exec >&3, o casco é eliminado quando o 3 não estiver aberto.
precisa saber é o seguinte
Pelo menos está trabalhando zshe bash. Você poderia fornecer o shell no qual a falha execcausou um exit?
dan
Sim. Em bashfazer set -o posixe tente novamente. Em zsh... eu acho que é uma questão de definir o env var POSIX_BUILTINSpara um valor não nulo - mas eu esqueço de imediato. De qualquer forma, zshnão é um shell que tenta conformidade com POSIX e, portanto, é definitivamente fora do padrão. Ambas as conchas evitam a compatibilidade com o que alguns acreditam ser conveniente.
precisa saber é o seguinte
Também está trabalhando no shell Bourne simples.
dan
No bash, com set -o posixuma tentativa é bem-sucedida.
dan
-1

Isso parece super fácil (ver comentários):

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

Como um extra ... O teste [-r file] não indica se algum dado está realmente esperando para ser lido (/ dev / null passa neste teste (consulte os comentários)).

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

É necessário algum número pequeno para o argumento de tempo limite (read -t) ou dados que precisam de algum cálculo podem ser perdidos. O teste legível ([-r arquivo]) é necessário ou o comando read será bombardeado se o arquivo não for legível. Na verdade, isso não lerá nenhum dado porque a contagem de bytes é zero (leia -N 0).

Paulo
fonte
se você pretende assumir um sistema Linux, pode também dar uma olhada /proc/<pid>/fdinfo/<fd>, que lista todos os modos de arquivos abertos em flags:- veja aqui . Por que a sua segunda parte (mesmo depois de corrigir o erro gritante): read -t .1 -N0 <&4não informa se há dados a serem lidos no fd 4: tente com 4</dev/null.
mosvy
E, [ -r /proc/$$/fd/$FD ]é claro, não informa se o descritor de arquivo $FDé legível, mas se o arquivo do qual foi aberto pode ser aberto novamente , com outro descritor de arquivo, para leitura:exec 7>/tmp/foo; [ -r /proc/$$/fd/7 ] && echo fd 7 can be read from && cat <&7
mosvy
-1

A questão é bastante antiga - mas de qualquer maneira - por que simplesmente não usar builtins?

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

Resultado:

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

Então, para responder à pergunta - sugeriria:

if [ -t 3 ]; then
  # File descriptor 3 is open
else
  # File descriptor 3 is not open
fi
Dimas
fonte
-tnão testa se um descritor de arquivo é válido, mas se está conectado a um tty. Coloque um prefixo a echo yup |no seu script e dirá que 0 is INVALID FD, embora de fato seja muito válido, um pipe.
mosvy 25/02