Ви вже отримали кілька дуже хороших відповідей. Дозвольте наголосити, що тут задіяні дві різні концепції, розуміння яких надзвичайно допомагає:
Фон: Дескриптор файлу та файлова таблиця
Дескриптор вашого файлу - це просто число 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
fork
від нового процесу.
- Відкрийте
file.txt
і збережіть його вказівник у дескрипторі файлів 1 (STDOUT).
- Вкажіть STDERR (дескриптор файлу 2) на те, на що зараз вказує fd 1 (що знову ж таки є вже відкритим
file.txt
).
exec
то <command>
Це, мабуть, перенаправляє stderr спочатку до stdout, а потім отриманий stdout переспрямовується на file.txt.
Це мало б сенс, якби була лише одна таблиця, але, як пояснено вище, є дві. Дескриптори файлів не вказують один на одного рекурсивно, немає сенсу думати "перенаправляти STDERR на STDOUT". Правильна думка - "вкажіть STDERR туди, куди точки STDOUT". Якщо пізніше ви зміните STDOUT, STDERR залишається там, де він є, він не магічно йде разом з подальшими змінами на STDOUT.