Quando você usaria um descritor de arquivo adicional?

74

Eu sei que você pode criar um descritor de arquivo e redirecionar a saída para ele. por exemplo

exec 3<> /tmp/foo # open fd 3.
echo a >&3 # write to it
exec 3>&- # close fd 3.

Mas você pode fazer o mesmo sem o descritor de arquivo:

FILE=/tmp/foo
echo a > "$FILE"

Estou procurando um bom exemplo de quando você precisaria usar um descritor de arquivo adicional.

dogbane
fonte

Respostas:

50

A maioria dos comandos possui um único canal de entrada (entrada padrão, descritor de arquivo 0) e um único canal de saída (saída padrão, descritor de arquivo 1) ou então opera em vários arquivos que eles abrem sozinhos (para você passar um nome de arquivo). (Isso é um acréscimo ao erro padrão (fd 2), que normalmente filtra todo o caminho até o usuário.) No entanto, às vezes é conveniente ter um comando que atue como um filtro de várias fontes ou de vários destinos. Por exemplo, aqui está um script simples que separa as linhas com números ímpares em um arquivo das linhas pares

while IFS= read -r line; do
  printf '%s\n' "$line"
  if IFS= read -r line; then printf '%s\n' "$line" >&3; fi
done >odd.txt 3>even.txt

Agora, suponha que você queira aplicar um filtro diferente às linhas com números ímpares e às linhas com números pares (mas não reuni-las novamente, isso seria um problema diferente, não possível do shell em geral). No shell, você pode canalizar apenas a saída padrão de um comando para outro comando; Para canalizar outro descritor de arquivo, você precisa redirecioná-lo para o fd 1 primeiro.

{ while  done | odd-filter >filtered-odd.txt; } 3>&1 | even-filter >filtered-even.txt

Outro caso de uso mais simples é filtrar a saída de erro de um comando .

exec M>&Nredireciona um descritor de arquivo para outro pelo restante do script (ou até outro comando alterar os descritores de arquivo novamente). Há alguma sobreposição na funcionalidade entre exec M>&Ne somecommand M>&N. O execformulário é mais poderoso, pois não precisa ser aninhado:

exec 8<&0 9>&1
exec >output12
command1
exec <input23
command2
exec >&9
command3
exec <&8

Outros exemplos que podem ser de interesse:

E para ainda mais exemplos:

PS Essa é uma pergunta surpreendente vinda do autor da publicação mais votada no site que usa o redirecionamento através do fd 3 !

Gilles
fonte
Eu prefiro dizer que "a maioria dos comandos possui um canal de saída único ou duplo - stdout (fd 1) e muitas vezes stderr (fd 2)".
rozcietrzewiacz
Além disso, você poderia explicar por que usa while IFS= read -r line;? A meu ver, o IFS não tem efeito aqui, pois você atribui valor a apenas uma variável ( linha ). Veja esta pergunta.
rozcietrzewiacz
@rozcietrzewiacz Fiz uma menção ao stderr e vejo a primeira parte da minha resposta para saber por que IFSfaz a diferença, mesmo que você esteja lendo uma variável única (é para manter o espaço em branco principal).
Gilles
Você não poderia fazer o mesmo sed -ne 'w odd.txt' -e 'n;w even.txt'?
Curinga
1
@Wildcard Você pode fazer o mesmo com outras ferramentas, com certeza. Mas o objetivo desta resposta era ilustrar os redirecionamentos no shell.
Gilles
13

Aqui está um exemplo do uso de FDs extras como controle de chattiness de script bash:

#!/bin/bash

log() {
    echo $* >&3
}
info() {
    echo $* >&4
}
err() {
    echo $* >&2
}
debug() {
    echo $* >&5
}

VERBOSE=1

while [[ $# -gt 0 ]]; do
    ARG=$1
    shift
    case $ARG in
        "-vv")
            VERBOSE=3
        ;;
        "-v")
            VERBOSE=2
        ;;
        "-q")
            VERBOSE=0
        ;;
        # More flags
        *)
        echo -n
        # Linear args
        ;;
    esac
done

for i in 1 2 3; do
    fd=$(expr 2 + $i)
    if [[ $VERBOSE -ge $i ]]; then
        eval "exec $fd>&1"
    else
        eval "exec $fd> /dev/null"
    fi
done

err "This will _always_ show up."
log "This is normally displayed, but can be prevented with -q"
info "This will only show up if -v is passed"
debug "This will show up for -vv"
Fordi
fonte
8

No contexto de pipes nomeados (fifos), o uso de um descritor de arquivo adicional pode ativar o comportamento de tubulação sem bloqueio.

(
rm -f fifo
mkfifo fifo
exec 3<fifo   # open fifo for reading
trap "exit" 1 2 3 15
exec cat fifo | nl
) &
bpid=$!

(
exec 3>fifo  # open fifo for writing
trap "exit" 1 2 3 15
while true;
do
    echo "blah" > fifo
done
)
#kill -TERM $bpid

Consulte: Pipe nomeado fechando prematuramente no script?

Chade
fonte
1
você desenterrou uma das minhas antigas perguntas :) chad está certo, você terá uma condição de corrida.
n0pe 17/08/11
6

Um descritor de arquivo extra é bom para quando você deseja capturar o stdout em uma variável e ainda assim deseja gravar na tela, por exemplo, em uma interface com o usuário do script bash

arg1 string to echo 
arg2 flag 0,1 print or not print to 3rd fd stdout descriptor   
function ecko3 {  
if [ "$2" -eq 1 ]; then 
    exec 3>$(tty) 
    echo -en "$1" | tee >(cat - >&3)
    exec 3>&- 
else 
    echo -en "$1"  
fi 
}
Adam Michael Danischewski
fonte
2
Sei que essa não é uma resposta nova, mas tive que encará-la por um tempo para ver o que ela faz e achava que seria útil se alguém adicionasse um exemplo dessa função sendo usada. Isso faz eco e captura toda a saída um comando - df, neste caso. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe
1

Exemplo: usando flock para forçar scripts a serem executados em série com bloqueios de arquivo

Um exemplo é usar o bloqueio de arquivos para forçar os scripts a serem executados em série no sistema. Isso é útil se você não quiser que dois scripts do mesmo tipo funcionem nos mesmos arquivos. Caso contrário, os dois scripts interfeririam entre si e possivelmente danificariam os dados.

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#open file descriptor 3 for writing
exec 3> /tmp/file.lock

#create an exclusive lock on the file using file descriptor 3
#exit if lock could not be obtained
flock -n 3

#execute serial code

#remove the file while the lock is still obtained
rm -f /tmp/file.lock

#close the open file handle which releases the file lock and disk space
exec 3>&-

Use o rebanho funcionalmente definindo bloqueio e desbloqueio

Você também pode agrupar essa lógica de bloqueio / desbloqueio em funções reutilizáveis. O seguinte trapshell incorporado liberará automaticamente o bloqueio de arquivo quando o script sair (erro ou êxito). trapajuda a limpar os bloqueios de arquivos. O caminho /tmp/file.lockdeve ser um caminho codificado para que vários scripts possam tentar travá-lo.

# obtain a file lock and automatically unlock it when the script exits
function lock() {
  exec 3> /tmp/file.lock
  flock -n 3 && trap unlock EXIT
}

# release the file lock so another program can obtain the lock
function unlock() {
  # only delete if the file descriptor 3 is open
  if { >&3 ; } &> /dev/null; then
    rm -f /tmp/file.lock
  fi
  #close the file handle which releases the file lock
  exec 3>&-
}

A unlocklógica acima é excluir o arquivo antes do bloqueio ser liberado. Dessa forma, ele limpa o arquivo de bloqueio. Como o arquivo foi excluído, outra instância deste programa pode obter o bloqueio do arquivo.

Uso de funções de bloqueio e desbloqueio em scripts

Você pode usá-lo em seus scripts como no exemplo a seguir.

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#try to lock (else exit because of non-zero exit code)
lock

#system-wide serial locked code

unlock

#non-serial code

Se você deseja que seu código aguarde até que seja capaz de bloquear, você pode ajustar o script como:

set -e

#wait for lock to be successfully obtained
while ! lock 2> /dev/null; do
  sleep .1
done

#system-wide serial locked code

unlock

#non-serial code
Sam Gleske
fonte
0

Como exemplo concreto, acabei de escrever um script que precisa das informações de tempo de um subcomando. O uso de um descritor de arquivo extra me permitiu capturar o timestderr do comando sem interromper o stdout ou o stderr do subcomando.

(time ls -9 2>&3) 3>&2 2> time.txt

O que isso faz é apontar lso stderr para fd 3, apontar fd 3 para o stderr do script e apontar timeo stderr para um arquivo. Quando o script é executado, seu stdout e stderr são os mesmos do subcomando, que podem ser redirecionados normalmente. Somente timea saída é redirecionada para o arquivo.

$ echo '(time ls my-example-script.sh missing-file 2>&3) 3>&2 2> time.txt' > my-example-script.sh
$ chmod +x my-example-script.sh 
$ ./my-example-script.sh 
ls: missing-file: No such file or directory
my-example-script.sh
$ ./my-example-script.sh > /dev/null
ls: missing-file: No such file or directory
$ ./my-example-script.sh 2> /dev/null
my-example-script.sh
$ cat time.txt

real    0m0.002s
user    0m0.001s
sys 0m0.001s
Ben Blank
fonte