Praktyczne zastosowanie do przenoszenia deskryptorów plików

16

Według strony podręcznika bash:

Operator przekierowania

   [n]<&digit-

przenosi deskryptor pliku digitdo deskryptora pliku nlub standardowego wejścia (deskryptor pliku 0), jeśli nnie jest określony. digitjest zamykany po skopiowaniu do n.

Co to znaczy „przenieść” deskryptor pliku na inny? Jakie są typowe sytuacje dla takiej praktyki?

Quentin
źródło

Odpowiedzi:

14

3>&4-jest rozszerzeniem ksh93 obsługiwanym również przez bash i to jest skrót od 3>&4 4>&-, czyli 3 teraz wskazuje na to, gdzie 4 kiedyś, a 4 jest teraz zamknięty, więc to, na co wskazywał 4, teraz zmieniło się na 3.

Typowym zastosowaniem są przypadki, w których zduplikowano stdinlub stdoutzapisano kopię i chcesz ją przywrócić, na przykład:

Załóżmy, że chcesz przechwycić stderr polecenia (i tylko stderr), pozostawiając stdout sam w zmiennej.

Podstawienie polecenia var=$(cmd), tworzy potok. Koniec zapisu potoku staje się standardowym cmdwyjściem (deskryptor pliku 1), a drugi koniec jest odczytywany przez powłokę w celu wypełnienia zmiennej.

Teraz, jeśli chcesz stderr, aby przejść do zmiennej, można zrobić: var=$(cmd 2>&1). Teraz zarówno fd 1 (stdout), jak i 2 (stderr) idą do potoku (i ostatecznie do zmiennej), co stanowi tylko połowę tego, czego chcemy.

Jeśli tak zrobimy var=$(cmd 2>&1-)(skrót od var=$(cmd 2>&1 >&-), teraz tylko cmdstderr idzie do potoku, ale fd 1 jest zamknięty. Jeśli cmdspróbuje zapisać dane wyjściowe, które zwrócą się z EBADFbłędem, jeśli otworzy plik, otrzyma pierwszy wolny plik fd i zostanie mu przypisany otwarty plik, stdoutchyba że polecenie go chroni! Nie tego też chcemy.

Jeśli chcemy, aby standardowe wyjście cmdzostało pozostawione w spokoju, to znaczy wskazywać na ten sam zasób, na który wskazywał poza podstawieniem polecenia, to musimy jakoś wprowadzić ten zasób do podstawienia polecenia. W tym celu możemy wykonać kopię stdout poza podstawieniem polecenia, aby wziąć ją do środka.

{
  var=$(cmd)
} 3>&1

Który jest czystszym sposobem na napisanie:

exec 3>&1
var=$(cmd)
exec 3>&-

(co ma również tę zaletę, że przywraca fd 3 zamiast go zamykać na końcu).

Następnie na {(lub exec 3>&1) i do }, zarówno fd 1, jak i 3 wskazują na ten sam zasób, na który początkowo wskazywał fd 1. fd 3 będzie również wskazywał na ten zasób w podstawieniu polecenia (podstawienie polecenia przekierowuje tylko fd 1, stdout). Tak więc powyżej cmdmamy dla fds 1, 2, 3:

  1. rura do var
  2. nietknięty
  3. to samo, co 1 wskazuje poza podstawieniem polecenia

Jeśli zmienimy to na:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

Potem staje się:

  1. to samo, co 1 wskazuje poza podstawieniem polecenia
  2. rura do var
  3. to samo, co 1 wskazuje poza podstawieniem polecenia

Teraz mamy to, czego chcieliśmy: stderr idzie do rury, a stdout pozostaje nietknięty. Jednak wyciekamy z tego Fd 3 do cmd.

Podczas gdy polecenia (zgodnie z konwencją) zakładają, że fds 0 do 2 są otwarte i są standardowym wejściem, wyjściem i błędem, nie zakładają niczego z innych fds. Najprawdopodobniej pozostawiają ten fd 3 nietknięty. Jeśli będą potrzebować innego deskryptora pliku, zrobią to, open()/dup()/socket()...co zwróci pierwszy dostępny deskryptor pliku. Jeśli (podobnie jak skrypt powłoki exec 3>&1) muszą tego użyć fd, najpierw przypisują go do czegoś (i w tym procesie zasoby posiadane przez nasz fd 3 zostaną zwolnione przez ten proces).

Dobrą praktyką jest zamknięcie tego fd 3, ponieważ cmdnie korzysta z niego, ale nie jest to żadna wielka sprawa, jeśli zostawimy go przydzielonym przed zadzwonieniem cmd. Problemy mogą być takie: że cmd(i potencjalnie inne procesy, które się odradza) ma do dyspozycji o jeden mniej FD. Potencjalnie poważniejszym problemem jest to, że zasób wskazany przez fd może zostać zatrzymany przez proces spawnowany przez to cmdw tle. Może to budzić obawy, jeśli ten zasób jest potokiem lub innym kanałem komunikacji międzyprocesowej (tak jak podczas uruchamiania skryptu jako script_output=$(your-script)), ponieważ oznacza to, że proces odczytu z drugiego końca nigdy nie zobaczy końca pliku, dopóki proces w tle kończy się.

Tutaj lepiej napisać:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

Które bashmożna skrócić do:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

Podsumowując powody, dla których jest rzadko używany:

  1. to niestandardowy i po prostu cukier syntaktyczny. Musisz zrównoważyć oszczędność kilku naciśnięć klawiszy, czyniąc skrypt mniej przenośnym i mniej oczywistym dla osób nie przyzwyczajonych do tej niezwykłej funkcji.
  2. Konieczność zamknięcia oryginalnego pliku fd po powieleniu jest często pomijana, ponieważ przez większość czasu nie cierpimy z powodu konsekwencji, więc po prostu robimy to >&3zamiast >&3-lub >&3 3>&-.

Dowód na to, że jest rzadko używany, jak się dowiedziałeś, jest fałszywy w bash . W bash compound-command 3>&4-lub any-builtin 3>&4-pozostawia fd 4 zamknięty nawet po compound-commandlub any-builtinwrócił. Patch naprawić problem jest teraz (19.02.2013) dostępne.

Stéphane Chazelas
źródło
Dzięki, teraz już wiem, czym jest przenoszenie fd. Mam 4 podstawowe pytania dotyczące drugiego fragmentu (i ogólnie fds): 1) W cmd1 tworzysz 2 (stderr), aby był kopią 3, co jeśli polecenie użyłoby tego 3 fd wewnętrznie? 2) Dlaczego działają 3> i 1 oraz 4> i 1? Powielanie 3 i 4 działa tylko na tych dwóch cmdach, czy dotyczy to również bieżącej powłoki? 3) Dlaczego zamykasz 4 w cmd1 i 3 w cmd2? Te polecenia nie używają wspomnianego fds, prawda? 4) Co się stanie, jeśli ostatni fragment kodu zostanie zduplikowany do nieistniejącego fragmentu (w cmd1 i cmd2), czyli odpowiednio 3 i 4?
Quentin
@Quentin, użyłem prostszego przykładu i wyjaśniłem nieco więcej, mając nadzieję, że teraz rodzi on mniej pytań niż odpowiedzi. Jeśli nadal masz pytania niezwiązane bezpośrednio z ruchomą składnią fd, proponuję zadać osobne pytanie
Stéphane Chazelas
{ var=$(cmd 2>&1 >&3) ; } 3>&1-Czy to nie literówka w zamykaniu 1?
Quentin,
@Quentin, To jest literówka w tym, że nie sądzę, że zamierzałem to uwzględnić, jednak nie robi to różnicy, ponieważ nic w nawiasach klamrowych nie używa tego fd 1 (to był cały sens powielania go do fd 3: ponieważ oryginał 1 inaczej nie byłyby dostępne w środku $(...)).
Stéphane Chazelas
1
@Quentin Po wejściu {...}, fd 3 wskazuje na to, co wskazało fd 1, a fd 1 jest zamykane, a następnie po wejściu $(...), fd 1 jest ustawiane na rurze, która się karmi $var, następnie na cmd2 również na to, a następnie 1 na jakie 3 punkty do, to jest zewnętrzne 1. Fakt, że 1 pozostaje zamknięty, jest błędem w bashu, zgłoszę to. ksh93, skąd pochodzi ta funkcja, nie ma tego błędu.
Stéphane Chazelas
4

Oznacza to, że wskazuje on to samo miejsce, co inny deskryptor pliku. Trzeba to zrobić bardzo rzadko, oprócz oczywistej oddzielnego postępowania standardowego deskryptora błędu ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Może się przydać w niektórych skomplikowanych przypadkach.

Przewodnik Advanced Bash Scripting zawiera następujący przykład poziomu dziennika i ten fragment kodu:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

W Source Mage's Sorcery używamy go na przykład do rozróżnienia różnych wyników z tego samego bloku kodu:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

Do celów logowania dołączono dodatkowe podstawianie procesów (VOYEUR decyduje, czy dane powinny być wyświetlane na ekranie, czy tylko rejestrować), ale niektóre komunikaty muszą być zawsze prezentowane. Aby to osiągnąć, drukujemy je do deskryptora pliku 3, a następnie obsługujemy go specjalnie.

lynxlynxlynx
źródło
0

W Uniksie pliki są obsługiwane przez deskryptory plików (małe liczby całkowite, na przykład standardowe wejście to 0, standardowe wyjście to 1, standardowy błąd to 2; gdy otwierasz inne pliki, zwykle otrzymują najmniejszy nieużywany deskryptor). Więc jeśli znasz wewnętrzne funkcje programu i chcesz wysłać dane wyjściowe, które trafiają do deskryptora pliku 5 do standardowego wyjścia, możesz przenieść deskryptor 5 do 1. Stąd 2> errorspochodzi, a konstrukcje lubią 2>&1kopiować błędy do strumień wyjściowy.

Tak więc prawie nigdy nie był używany (niejasno pamiętam, że użyłem go raz lub dwa razy w gniewie w ciągu moich ponad 25 lat prawie wyłącznego używania Uniksa), ale kiedy jest to absolutnie konieczne.

vonbrand
źródło
Ale dlaczego nie powielić deskryptora pliku 1 w następujący sposób: 5> i 1? Nie rozumiem, jaki jest pożytek z przenoszenia FD, ponieważ jądro ma zamiar je zamknąć zaraz po ...
Quentin
To nie powiela 5 na 1, wysyła 5 do dokąd zmierza 1. I zanim zapytasz; tak, istnieje kilka różnych zapisów, wybranych z różnych powłok prekursorów.
vonbrand
Nadal nie rozumiem. Jeśli 5>&1wysyła 5 do dokąd 1 zmierza, to co dokładnie robi 1>&5-oprócz zamykania 5?
Quentin