Практичне використання для переміщення дескрипторів файлів


16

Згідно зі сторінкою bash man:

Оператор перенаправлення

   [n]<&digit-

переміщує дескриптор файлу digitдо дескриптора файлу nабо стандартного вводу (дескриптор файлу 0), якщо nйого не вказано. digitзакривається після дублювання n.

Що означає "перенести" дескриптор файлу на інший? Які типові ситуації для такої практики?

Відповіді:


14

3>&4-є розширенням ksh93, яке також підтримується bash, і це скорочено 3>&4 4>&-, тобто 3 зараз вказує на те, де раніше було 4, а 4 зараз закрито, тому те, на що вказували 4, тепер перейшло до 3.

Типовим є використання у тих випадках, коли ви копіювали stdinабо stdoutзберігаєте його копію та хочете відновити її, наприклад у:

Припустимо, ви хочете зафіксувати stderr команди (і тільки stderr), залишаючи stdout в змінній.

Заміна команди var=$(cmd), створює трубу. Кінець запису труби стає cmdstdout (дескриптор файлу 1), а інший кінець зчитується оболонкою для заповнення змінної.

Тепер, якщо ви хочете , stderrщоб перейти до змінної, ви можете зробити: var=$(cmd 2>&1). Тепер і fd 1 (stdout), і 2 (stderr) переходять до труби (і, зрештою, до змінної), що становить лише половину від того, що ми хочемо.

Якщо ми зробимо var=$(cmd 2>&1-)(короткий для var=$(cmd 2>&1 >&-), тепер тільки cmd"stderr" переходить до труби, але fd 1 закритий. Якщо cmdспробує написати який-небудь вихід, він повернеться з EBADFпомилкою, якщо він відкриє файл, він отримає перший безкоштовний fd і відкритий файл йому буде призначений, stdoutякщо тільки команда не захищає цього! Не те, чого ми хочемо.

Якщо ми хочемо, щоб stdout cmdзалишався в спокої, тобто вказував на той самий ресурс, на який він вказував за межами заміни команди, тоді нам потрібно якось занести цей ресурс всередину заміни команди. Для цього ми можемо зробити копію stdout зовнішньої підстановки команди, щоб перенести її всередину.

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

Який чистіший спосіб писати:

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

(що також має перевагу відновити fd 3, а не закривати його врешті-решт).

Потім на {(або exec 3>&1) і до }, і fd 1, і 3 вказують на один і той же ресурс fd 1, на який вказували спочатку. fd 3 також вкаже на цей ресурс всередині підстановки команд (заміна команди перенаправляє лише fd 1, stdout). Отже вище, для cmd, ми маємо для FDS 1, 2, 3:

  1. труба до вар
  2. недоторканий
  3. те саме, що 1 вказує на заміну команди

Якщо ми змінимо його на:

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

Потім це стає:

  1. те саме, що 1 вказує на заміну команди
  2. труба до вар
  3. те саме, що 1 вказує на заміну команди

Тепер ми отримали те, що хотіли: stderr йде до труби, а stdout залишається недоторканим. Однак ми просочуємо цей fd 3 to cmd.

Хоча команди (за умовою) передбачають, що FDS 0 до 2 є відкритими і є стандартними введеннями, виводами та помилками, вони не припускають нічого іншого з інших FDS. Швидше за все, вони залишать цей fd 3 недоторканим. Якщо їм потрібен інший дескриптор файлу, вони просто зроблять те, open()/dup()/socket()...що поверне перший доступний дескриптор файлу. Якщо (як сценарій оболонки, який це робиться exec 3>&1) їм потрібно використовувати це fdспеціально, вони спочатку призначать це чомусь (і в цьому процесі ресурс, який міститься в нашому fd 3, буде випущений цим процесом).

Добре практично закрити цей fd 3, оскільки cmdвін не використовує його, але нічого особливого, якщо ми залишимо його присвоєним перед тим, як зателефонувати cmd. Проблеми можуть полягати в тому, що cmd(і, можливо, інші процеси, які він породжує) має один менший FD. Потенційно більш серйозна проблема полягає в тому, якщо ресурс, на який вказує цей fd, утримується процесом, породженим cmdу фоновому режимі. Може викликати занепокоєння, якщо цей ресурс - це труба або інший міжпроцесорний канал зв'язку (наприклад, коли ваш сценарій працює як script_output=$(your-script)), оскільки це означає, що процес читання з іншого кінця ніколи не побачить кінець файлу до цього фоновий процес припиняється.

Тож тут краще написати:

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

Що, з, bashможна скоротити до:

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

Підсумовуючи причини, чому його рідко використовують:

  1. це нестандартний і просто синтаксичний цукор. Ви повинні збалансувати економію кількох натискань клавіш, зробивши сценарій менш портативним і менш очевидним для людей, які не звикли до цієї нечастої функції.
  2. Необхідність закрити оригінальний fd після його дублювання часто ігнорується, оскільки більшість часу ми не страждаємо від цього наслідку, тому просто робимо >&3замість >&3-або >&3 3>&-.

Доказом того, що його рідко використовують, як ви з’ясували, є те, що він багнійний у баш . У bash compound-command 3>&4-або any-builtin 3>&4-листя fd 4 закритий навіть після compound-commandабо any-builtinповернувся. Налагоджено виправлення для вирішення проблеми (2013-02-19).


Дякую, тепер я знаю, що таке переміщення FD. У мене є 4 основні питання щодо 2-го фрагмента (і FDS загалом): 1) У cmd1 ви робите 2 (stderr) копією 3, що робити, якщо команда використовує внутрішнє це 3 fd? 2) Чому 3> & 1 і 4> & 1 працюють? Дублювання 3 та 4 набуває чинності лише у цих двох смд, чи також впливає поточна оболонка? 3) Чому ви закриваєте 4 у cmd1 та 3 у cmd2? Ці команди не використовують згадані fds, чи не так? 4) В останньому фрагменті, що відбувається, якщо fd дублюється на неіснуючий (у cmd1 та cmd2), я маю на увазі відповідно 3 та 4?
Квентін

@Quentin, я використав більш простий приклад і пояснив трохи більше, сподіваючись, що це зараз викликає менше питань, ніж відповідає. Якщо у вас все ще є запитання, не пов’язані безпосередньо з синтаксисом переміщення fd, пропоную вам задати окреме запитання
Stéphane Chazelas

{ var=$(cmd 2>&1 >&3) ; } 3>&1-Хіба це не помилка в закритті 1?
Квентін

@Quentin, це помилка в тому , що я не думаю , що я мав намір включити його, проте це не має ніякого значення , так як нічого в фігурних дужках використань , що Fd 1 (він був весь сенс дублювати його Fd 3: тому що оригінал 1 інакше не буде доступним всередині $(...)).
Стефан Шазелас

1
@Quentin Після введення {...}fd 3 вказує на те, що fd 1 використовував для точки, а fd 1 закрито, потім при введенні $(...)fd 1 встановлюється на трубу, що подає $var, потім також на cmd2 до цієї, а потім 1 на 3 точки до, це зовнішній 1. Про те, що 1 залишається закритим після цього, це помилка в баші, я повідомлю про це. ksh93, звідки походить ця функція, не має цієї помилки.
Стефан Шазелас

4

Це означає, щоб вказати на те саме місце, що і інший дескриптор файлів. Ви повинні зробити це дуже рідко, окрім очевидної окремої обробки стандартного дескриптора помилок ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Це може стати в нагоді в деяких складних випадках.

У посібнику з розширеного сценарію Bash є такий приклад більш тривалого журналу та цей фрагмент:

# 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.

Наприклад, у джерелах чародійства Mage ми використовуємо його для виявлення різних результатів з одного блоку коду:

  (
    # 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)

У ньому є додаткова заміна процесів, додана з журнальних причин (VOYEUR вирішує, чи слід відображати дані на екрані чи просто журнал), але деякі повідомлення потрібно завжди представляти. Для цього ми друкуємо їх до дескриптора 3, а потім спеціально обробляємо його.


0

У Unix файли обробляються дескрипторами файлів (малі цілі числа, наприклад, стандартний вхід - 0, стандартний вихід - 1, стандартна помилка - 2; при відкритті інших файлів їм зазвичай присвоюється найменший невикористаний дескриптор). Отже, якщо ви знаєте, які саме програми програми, і ви хочете надіслати вихідний результат, який надходить до дескриптора файлу 5, до стандартного виводу, ви перемістите дескриптор 5 до 1. Ось звідки 2> errorsпоходить, і конструкції, як 2>&1дублювати помилки, у вихідний потік.

Отже, майже ніколи не використовувався (я тумано пам’ятаю, що використовував його один раз або два рази в гніві за мої 25+ років майже ексклюзивного використання Unix), але коли це було абсолютно важливо.


Але чому б не дублювати дескриптор файлу 1 таким чином: 5> & 1? Я не розумію, в чому користь переміщення FD, оскільки ядро ​​збирається закрити його відразу після ...
Квентін

Це не дублює 5 в 1, воно посилає 5 туди, куди йде 1. І перш ніж запитати; так, є кілька різних позначень, зібраних із різних оболонок попередника.
vonbrand

Ще не зрозуміти. Якщо 5>&1посилає 5 туди, куди йде 1, то що саме 1>&5-робити, окрім закриття 5?
Квентін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.