Quando você usaria um descritor de arquivo adicional?

60

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 a mesma coisa sem o descritor de arquivo:

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

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

    
por dogbane 17.08.2011 / 12:54

7 respostas

40

A maioria dos comandos tem 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 mesmos abrem (assim você passa um nome de arquivo ). (Isso é um acréscimo do erro padrão (fd 2), que geralmente é filtrado até o usuário.) No entanto, às vezes é conveniente ter um comando que atua como um filtro de várias origens ou para vários destinos. Por exemplo, aqui está um script simples que separa as linhas í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 de números ímpares e às linhas pares (mas não colocá-las juntas novamente, isso seria um problema diferente, não viável do shell em geral). No shell, você só pode canalizar 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 é que filtra o erro saída de um comando .

exec M>&N redireciona um descritor de arquivo para outro para o restante do script (ou até que outro comando altere os descritores de arquivo novamente). Há alguma sobreposição na funcionalidade entre exec M>&N e somecommand M>&N . A forma exec é mais poderosa, pois não precisa ser aninhada:

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

Outros exemplos que podem ser de interesse:

E ainda mais exemplos:

P.S. Esta é uma pergunta surpreendente vinda do autor do post mais votado no site que usa redirecionamento por meio do fd 3 !

    
por 17.08.2011 / 14:34
9

Veja um exemplo do uso de FDs extras como controle de chattiness do 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"
    
por 25.07.2015 / 23:37
8

No contexto de pipes nomeados (fifos), o uso de um descritor de arquivo adicional pode ativar o comportamento de piping 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

Veja: Canal nomeado fechando prematuramente no script?

    
por 17.08.2011 / 13:44
4

Um descritor de arquivo extra é bom para quando você deseja capturar o stdout em uma variável, mas ainda assim deseja gravar na tela, por exemplo, em uma interface de 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 
}
    
por 29.05.2014 / 00:49
3

Aqui está outro cenário quando usar um descritor de arquivo adicional parece apropriado (no Bash):

Segurança da senha do script shell dos parâmetros de linha de comando

env -i bash --norc   # clean up environment
set +o history
read -s -p "Enter your password: " passwd
exec 3<<<"$passwd"
mycommand <&3  # cat /dev/stdin in mycommand
    
por 24.08.2011 / 10:26
0

Exemplo: usando o flock para forçar scripts a serem executados serialmente com bloqueios de arquivos

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

#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 floco funcionalmente definindo bloqueio e desbloqueio

Você também pode envolver essa lógica de bloqueio / desbloqueio em funções reutilizáveis. Os seguintes trap shell embutidos serão liberados automaticamente o bloqueio de arquivo quando o script sai (erro ou sucesso). trap ajuda a limpar seus bloqueios de arquivos. O caminho /tmp/file.lock deve ser um caminho codificado para que vários scripts possam tentar bloqueá-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 lógica unlock acima é para excluir o arquivo antes que o bloqueio seja liberado. Desta forma, limpa o arquivo de bloqueio. Como o arquivo foi excluído, outra instância deste programa é capaz de obter o bloqueio de 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ê quiser que seu código aguarde até que seja possível 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
    
por 20.10.2017 / 05:20
0

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

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

O que isso faz é apontar o stderr de ls para o fd 3, apontar o fd 3 para o stderr do script e apontar o stderr do time para um arquivo. Quando o script é executado, sua stdout e stderr são iguais às do subcomando, que podem ser redirecionadas como de costume. Apenas a saída de time é 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
    
por 23.11.2017 / 00:57