У коді “{exec> / dev / null; }> / dev / null »що відбувається під кришкою?


15

Коли ви переспрямовуєте список команд, який містить перенаправлення exec, exec> / dev / null, схоже, не застосовується після цього, як, наприклад, з:

{ exec >/dev/null; } >/dev/null; echo "Hi"

Друкується "Привіт".

У мене склалося враження, що {}список команд не вважається підзаголовком, якщо він не є частиною конвеєра, тому exec >/dev/nullслід все-таки застосовуватись у поточному середовищі оболонки.

Тепер якщо ви зміните його на:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"

немає результату, як очікувалося; Дескриптор файлу 1 залишається вказаним на / dev / null і для майбутніх команд. Це показано за допомогою повторного:

{ exec >/dev/null; } >/dev/null; echo "Hi"

що не дасть результату.

Я спробував створити сценарій і прострочити його, але я все ще не впевнений, що саме тут відбувається.

Що в цьому пункті цього сценарію відбувається з дескриптором файлу STDOUT?

EDIT: Додавання мого виведення страз:

read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
close(10)                               = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90)        = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3)                     = 3

Це дивно; Я не можу відтворити зображення close(10). Чи можете ви також розмістити весь вміст сценарію, на який ви стикалися?
ДепресіяДаніель

@DepressedDaniel Ось повний скрипт і Трасування: сценарій Трасування
Joey Pabalinas

У вас є заблудлі ;після }, який змінює значення > /dev/nullне застосовується до списку з'єднання {}після того, як всі.
ДепресіяДаніель

@DepressDaniel Ах, ви абсолютно праві! Тепер результат - це те, чого я очікую; дякую за відповіді!
Джої Пабалінас

Відповіді:


17

Давайте слідкуємо

{ exec >/dev/null; } >/dev/null; echo "Hi"

крок за кроком.

  1. Є дві команди:

    а. { exec >/dev/null; } >/dev/null, за яким

    б. echo "Hi"

    Оболонка виконує спочатку команду (a), а потім команду (b).

  2. Виконання { exec >/dev/null; } >/dev/nullвиручки наступним чином:

    а. По-перше, оболонка виконує перенаправлення >/dev/null і пам'ятає скасувати її, коли команда закінчується .

    б. Потім оболонка виконується { exec >/dev/null; }.

    c. Нарешті, оболонка перемикає стандартний висновок туди, де була. (Це той самий механізм, що і ls -lR /usr/share/fonts >~/FontList.txtперенаправлення робиться лише на час дії команди, до якої вони належать.)

  3. Після виконання першої команди оболонка виконується echo "Hi". Стандартний вихід є там, де він був до першої команди.


Чи є якась причина, чому 2a виконується перед 2b? (справа наліво)
Joey Pabalinas

5
Перенаправлення повинні бути виконані перед командою, до якої вони застосовуються, ні? Як вони могли працювати інакше?
AlexP

Ага, ніколи про це не думав! Перші два - це великі відповіді; дати це трохи, перш ніж зважитися на одне, але я ціную обидва пояснення!
Joey Pabalinas

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

14

Щоб не використовувати підзаголовки чи підпроцеси, коли вихід списку складених {}є конвеєрним >, оболонка зберігає дескриптор STDOUT перед запуском списку з'єднань та відновлює його після. Таким чином, exec >у складі списку складових не відбувається його вплив через точку, коли старий дескриптор відновлюється як STDOUT.

Давайте розглянемо відповідну частину strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n:

   132  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   133  fcntl(1, F_GETFD)                       = 0
   134  fcntl(1, F_DUPFD, 10)                   = 10
   135  fcntl(1, F_GETFD)                       = 0
   136  fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
   137  dup2(3, 1)                              = 1
   138  close(3)                                = 0
   139  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   140  fcntl(1, F_GETFD)                       = 0
   141  fcntl(1, F_DUPFD, 10)                   = 11
   142  fcntl(1, F_GETFD)                       = 0
   143  fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
   144  dup2(3, 1)                              = 1
   145  close(3)                                = 0
   146  close(11)                               = 0
   147  dup2(10, 1)                             = 1
   148  fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
   149  close(10)                               = 0

Ви можете бачити, як у рядку 134 дескриптор 1( STDOUT) копіюється на інший дескриптор з принаймні індексом 10(саме F_DUPFDце і є; він повертає найнижчий доступний дескриптор, починаючи з заданого числа після дублювання на цей дескриптор). Також дивіться, як у рядку 137 результат open("/dev/null")(дескриптор 3) копіюється на дескриптор 1( STDOUT). Нарешті, в рядку 147старий STDOUTзбережений на дескрипторі 10копіюється назад в дескриптор 1( STDOUT). Чистий ефект полягає в ізоляції зміни STDOUTна лінію 144(що відповідає внутрішній exec >/dev/null).


Оскільки FD 1 перезаписаний FD 3 у рядку 137, чому рядок 141 не вказує 10 на / dev / null?
Joey Pabalinas

Рядок 141 @JoeyPabalinas дублює FD 1 (тобто stdout) на наступний доступний дескриптор після 10 , що виявляється 11, як ви бачите у зворотному значенні цього системного виклику. 10 просто закодовано в bash, щоб збереження дескриптора bash не заважало однозначним дескрипторам, якими ви можете маніпулювати у своєму сценарії через exec.
ДепресіяДаніель

Тож тоді fcntl (1, F_DUPFD, 10) завжди буде посилатися на STDOUT незалежно від того, де в даний час вказується FD 1?
Joey Pabalinas

@JoeyPabalinas Не впевнений, у чому ваше питання. FD 1 IS STDOUT. Вони те саме.
ДепресіяДаніель

До моєї оригінальної публікації було додано повний страйк.
Joey Pabalinas

8

Різниця між { exec >/dev/null; } >/dev/null; echo "Hi"та { exec >/dev/null; }; echo "Hi"полягає в тому, що подвійне перенаправлення робиться dup2(10, 1);перед закриттям fd 10, що є копією оригіналу stdout, перед запуском наступної команди ( echo).

Так трапляється, тому що зовнішнє переадресація насправді перекриває внутрішнє переспрямування. Ось чому він копіює назад оригінальний stdoutfd після його завершення.


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