У якому порядку оболонка виконує команди та перенаправляє потоки?


32

Я намагався перенаправити stdoutі stderrфайл, і сьогодні, і натрапив на це:

<command> > file.txt 2>&1

Це , по- видимому перенаправляє stderrдо stdoutпершого, а потім отриманий stdoutперенаправляється file.txt.

Однак чому це не замовлення <command> 2>&1 > file.txt? Можна, природно, читати це як (припускаючи виконання зліва направо) команду, що виконується спочатку, stderrперенаправлення на, stdoutа потім, в результаті чого stdoutзаписується file.txt. Але вищесказане лише переспрямовує stderrна екран.

Як оболонка інтерпретує обидві команди?


7
TLCL говорить про це "Спочатку ми перенаправляємо стандартний вихід у файл, а потім перенаправляємо дескриптор файлу 2 (стандартна помилка) на дескриптор файлу один (стандартний вихід)" та "Зауважте, що порядок переадресацій є значним. Перенаправлення стандартної помилки завжди має відбуватися після перенаправлення стандартного виводу, або він не працює "
Zanna

@Zanna: Так, моє запитання точно виникло з того, що я прочитав це в TLCL! :) Мені цікаво знати, чому це не працює, тобто як оболонка інтерпретує команди в цілому.
Потяг Heartnet

Ну, у своєму запитанні ви говорите навпаки "Це, мабуть, перенаправляє stderr до stdout спочатку ..." - що я розумію з TLCL, це те, що оболонка надсилає stdout у файл, а потім stderr в stdout (тобто у файл). Моя інтерпретація полягає в тому, що якщо ви надішліть stderr в stdout, він буде відображений в терміналі, і подальше перенаправлення stdout не буде включати stderr (тобто перенаправлення stderr на екран завершиться до того, як відбудеться перенаправлення stdout?)
Zanna

7
Я знаю, що це старомодна річ, але ваша оболонка постачається з посібником, який пояснює ці речі - наприклад, перенаправлення в bashпосібнику . До речі, перенаправлення - це не команди.
Reinier Post

Команда не може бути виконана до встановлення переадресації: Коли execv-family syscall викликається, щоб фактично передати підпроцес в команду, що починається, оболонка виходить із циклу (більше не має коду, який виконується в цьому процесі ) і не має можливості контролювати те, що відбувається з цього моменту; таким чином, всі переадресації повинні бути виконані перед початком виконання, в той час як оболонка має копію самої себе, яка виконується під час fork()запуску вашої команди.
Чарльз Даффі

Відповіді:


41

Під час запуску <command> 2>&1 > file.txtstderr переспрямовується 2>&1туди, куди зараз йде stdout, ваш термінал. Після цього stdout переспрямовується у файл >, але stderr не перенаправляється з ним, тому залишається як термінальний вихід.

За допомогою <command> > file.txt 2>&1stdout спочатку переспрямовується у файл >, потім 2>&1перенаправляє stderr туди, куди йде stdout, який файл.

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


Це має сенс, якщо ви думаєте, що з точки зору файлових дескрипторів і "dup / fdreopen" дзвінків виконуються в порядку зліва направо
Марк К Кован

20

Це може мати сенс, якщо простежити це.

На початку, stderr і stdout переходять до того ж самого (зазвичай терміналу, який я тут називаю pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Я маю на увазі stdin, stdout та stderr за їх номерами дескрипторів файлів : вони є дескрипторами файлів 0, 1 і 2 відповідно.

Тепер у першому наборі переадресацій ми маємо > file.txtі 2>&1.

Так:

  1. > file.txt: fd/1тепер переходить до file.txt. З >, 1дескриптор мається на увазі, коли нічого не вказано, так це 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2тепер іде туди, куди fd/1 зараз йде:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

З іншого боку, з 2>&1 > file.txtпорядком, який змінюється:

  1. 2>&1: fd/2тепер іде туди, куди fd/1зараз йде, а це означає, що нічого не змінюється:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1тепер переходить до file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

Важливим моментом є те, що перенаправлення не означає, що дескриптор перенаправленого файлу буде слідувати всім майбутнім змінам дескриптора цільового файлу; воно буде приймати лише поточний стан.


Дякую, це здається більш природним поясненням! :) Ви зробили невелику помилку, хоча у 2.другій частині; fd/1 -> file.txtі ні fd/2 -> file.txt.
Потяг Heartnet

11

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

Про це говорить командний рядок Linux Вільяма Шоттса

Спочатку ми перенаправляємо стандартний вихід у файл, а потім перенаправляємо дескриптор файлу 2 (стандартна помилка) на дескриптор одного файлу (стандартний вихід)

це має сенс, але тоді

Зауважте, що порядок переадресацій є важливим. Перенаправлення стандартної помилки завжди має відбуватися після перенаправлення стандартного виводу або воно не працює

але насправді ми можемо перенаправити stdout на stderr після перенаправлення stderr до файлу з тим же ефектом

$ uname -r 2>/dev/null 1>&2
$ 

Таким чином, command > file 2>&1оболонка надсилає stdout у файл, а потім надсилає stderr в stdout (який надсилається у файл). Тоді як в command 2>&1 > fileоболонці спочатку перенаправляє stderr на stdout (тобто відображає його в терміналі, куди зазвичай йде stdout), а потім після цього перенаправляє stdout у файл. TLCL вводить в оману, кажучи, що спочатку треба перенаправити stdout: оскільки ми можемо спершу перенаправити stderr у файл, а потім надіслати йому stdout. Що ми не можемо зробити, це перенаправити stdout на stderr або навпаки перед перенаправленням у файл. Ще один приклад

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Ми можемо подумати, що це видалить stdout на те саме місце, що і stderr, але це не так, воно перенаправляє спочатку stdout на stderr (екран), а потім лише перенаправляє stderr, як коли ми намагалися це навпаки ...

Я сподіваюся, що це принесе трохи світла ...


Так набагато красномовніше!
Арронічний

Ах, тепер я розумію! Дякую вам, @Zanna та @Arronical! Тільки починаю з моєї мандрівки командного рядка. :)
Потяг Heartnet

@TrainHeartnet це задоволення! Сподіваюся, вам сподобається так само, як і мені: D
Zanna

@Zanna: Дійсно, я! : D
поїзд Heartnet

2
@TrainHeartnet не хвилюйся, на тебе чекає світ розчарування та радості!
Арронічний

10

Ви вже отримали кілька дуже хороших відповідей. Дозвольте наголосити, що тут задіяні дві різні концепції, розуміння яких надзвичайно допомагає:

Фон: Дескриптор файлу та файлова таблиця

Дескриптор вашого файлу - це просто число 0 ... n, що є індексом у таблиці дескрипторів файлів у вашому процесі. За умовою, STDIN = 0, STDOUT = 1, STDERR = 2 (зауважте, що терміни STDINтощо - це лише символи / макроси, що використовуються умовно в деяких мовах програмування та на сторінках man, не існує фактичного "об'єкта", який називається STDIN; мета цього обговорення, STDIN - 0 тощо).

Ця таблиця дескрипторів файлу сама по собі не містить жодної інформації про те, що є фактичним файлом. Натомість він містить вказівник на іншу таблицю файлів; останній містить інформацію про фактичний фізичний файл (або блоковий пристрій, або трубу, або все інше, на що Linux може звертатися через файловий механізм) та додаткову інформацію (тобто читання чи запис).

Отже, коли ви використовуєте >або <в оболонці, ви просто замінюєте вказівник відповідного дескриптора файлу, щоб вказати на щось інше. Синтаксис 2>&1просто вказує дескриптор 2 куди б не було 1 бала. > file.txtпросто відкривається file.txtдля запису і дозволяє STDOUT (файловий дескриптор 1) вказувати на це.

Є й інші привілеї, наприклад 2>(xxx) (наприклад: створити новий процес, що працює xxx, створити трубу, підключити дескриптор файлу 0 нового процесу до зчитувального кінця труби та підключити дескриптор 2 файлу оригінального процесу до кінця запису труба).

Це також основа для "магії обробки файлів" в іншому програмному забезпеченні, крім вашої оболонки. Наприклад, ви можете у своєму скрипті Perl dupвиписати дескриптор файлу STDOUT в інший (тимчасовий), а потім повторно відкрити STDOUT до новоствореного тимчасового файлу. З цього моменту весь вихід STDOUT з вашого власного сценарію Perl та всі system()виклики цього сценарію потраплять у цей тимчасовий файл. Закінчивши, ви можете dupповернути STDOUT до тимчасового дескриптора, до якого ви зберегли його, і попередньо встановити все, як раніше. Тим часом можна навіть записати у цей тимчасовий дескриптор, тож поки ваш фактичний вихід STDOUT переходить до тимчасового файлу, ви все ще можете фактично виводити речі в реальний STDOUT (зазвичай, користувач).

Відповідь

Щоб застосувати наведену вище довідкову інформацію до свого питання:

У якому порядку оболонка виконує команди та перенаправляє потоки?

Зліва направо.

<command> > file.txt 2>&1

  1. fork від нового процесу.
  2. Відкрийте file.txtі збережіть його вказівник у дескрипторі файлів 1 (STDOUT).
  3. Вкажіть STDERR (дескриптор файлу 2) на те, на що зараз вказує fd 1 (що знову ж таки є вже відкритим file.txt).
  4. exec то <command>

Це, мабуть, перенаправляє stderr спочатку до stdout, а потім отриманий stdout переспрямовується на file.txt.

Це мало б сенс, якби була лише одна таблиця, але, як пояснено вище, є дві. Дескриптори файлів не вказують один на одного рекурсивно, немає сенсу думати "перенаправляти STDERR на STDOUT". Правильна думка - "вкажіть STDERR туди, куди точки STDOUT". Якщо пізніше ви зміните STDOUT, STDERR залишається там, де він є, він не магічно йде разом з подальшими змінами на STDOUT.


Запрошення на біт "файлової магії" - хоча це не відповідає безпосередньо на питання, я сьогодні дізнався щось нове ...
Флоріс

3

Порядок зліва направо. Посібник Баша вже охопив те, що ви запитуєте. Цитата з REDIRECTIONрозділу посібника:

   Redirections  are  processed  in  the
   order they appear, from left to right.

і кілька рядків пізніше:

   Note that the order of redirections is signifi
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

Важливо зазначити, що перенаправлення вирішується спочатку перед запуском будь-яких команд! Дивіться https://askubuntu.com/a/728386/295286


3

Це завжди зліва направо ... крім випадків, коли

Як і в математиці, ми робимо зліва направо, за винятком множення і ділення, перш ніж додавання і віднімання, за винятком операцій всередині дужок (+ -), які б виконувалися до множення і ділення.

Відповідно до посібника для початківців Bash ( Посібник для початківців Bash ) існує 8 порядків ієрархії того, що спочатку (перед зліва направо):

  1. Розширення дужки "{}"
  2. Розширення Tilde "~"
  3. Параметр оболонки та змінний вираз "$"
  4. Заміна команди "$ (команда)"
  5. Арифметичний вираз "$ ((ЕКСПРЕСІЯ))"
  6. Обробляти заміну, про що ми говоримо тут "<(СПИСОК)" або "> (СПИСОК)"
  7. Розщеплення слів "" <пробіл> <вкладка> <новий рядок> '"
  8. Розширення імені файлу "*", "?" Тощо.

Тому завжди зліва направо ... крім випадків, коли ...


1
Щоб було зрозуміло: підміна процесу та перенаправлення - це дві незалежні операції. Можна обійтися і без іншого. Заміна процесу заміни не означає, що Баш вирішує змінити порядок, в якому обробляє перенаправлення
muru
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.