Правило для отримання додаткової оболонки в Bash?


24

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

Однак, схоже, це не так. У фрагменті коду A (нижче) друга sleepкоманда не виконується в окремій оболонці (як визначено pstreeв іншому терміналі). Тим НЕ менше, в фрагменті коду В, друга sleepкоманда робить працювати в окремій оболонці. Єдина відмінність між фрагментами полягає в тому, що другий фрагмент має дві команди в дужках.

Може хтось, будь ласка, пояснить це правило, коли створюються підрозділи?

КОД SNIPPET A:

sleep 5
(
sleep 5
)

КОД SNIPPET B:

sleep 5
(
x=1
sleep 5
)

Відповіді:


20

В дужках завжди починається підзаголовок. Те, що відбувається, полягає в тому, що bash виявляє, що sleep 5це остання команда, виконана цією підзарядкою, тому він викликає execзамість fork+ exec. sleepКоманда замінює подоболочкі в тому ж процесі.

Іншими словами, базовий випадок:

  1. ( … )створити підзаділ. Оригінальний процес викликає forkі wait. У підпроцесі, який є підзаголовком:
    1. sleepце зовнішня команда, яка вимагає підпроцесу підпроцесу. Підрозділ дзвонить forkі wait. У підпроцесі:
      1. Підпроцес виконує зовнішню команду → exec.
      2. Врешті команда припиняється → exit.
    2. wait завершує в передпласті.
  2. wait завершує в оригінальному процесі.

Оптимізація:

  1. ( … )створити підзаділ. Оригінальний процес викликає forkі wait. У підпроцесі, який є підзаголовком, поки він не викликаєexec :
    1. sleep це зовнішня команда, і це останнє, що цей процес повинен зробити.
    2. Підпроцес виконує зовнішню команду → exec .
    3. Врешті команда припиняється → exit.
  2. wait завершує в оригінальному процесі.

Коли ви додаєте щось інше після дзвінка, sleep , передплату потрібно тримати навколо, тому така оптимізація не може відбутися.

Коли ви додаєте щось інше до виклику sleep, оптимізація може бути здійснена (і ksh робить це), але bash цього не робить (це дуже консервативно з цією оптимізацією).


Subshell створюється за допомогою виклику forkі створюється дочірній процес (виконувати зовнішні команди) шляхом викликуfork + exec . Але ваш перший пункт говорить про те, що fork + execзакликається і для передплати. Що я тут помиляюся?
хакі

1
@haccks fork+ execне викликається для підпрограми, вона викликається для зовнішньої команди. Без оптимізації є forkвиклик для нижньої частини і ще один для зовнішньої команди. Я додав детальний опис потоку до своєї відповіді.
Жил "ТАК - перестань бути злим"

Дякую тонну за оновлення. Тепер це пояснює краще. Я можу зробити з цього висновок, що у випадку (...)(у базовому випадку), може бути, а може і не бути, виклик execзалежить від того, чи має в підпакеті якусь зовнішню команду для виконання, тоді як у випадку виконання будь-якої зовнішньої команди вона повинна бути fork + exec.
хакі

Ще одне питання: чи працює ця оптимізація лише для підзарядки чи це може бути зроблено для команди, як dateу оболонці?
хакі

@haccks Я не розумію питання. Ця оптимізація стосується виклику зовнішньої команди як останнього, що робиться в оболонці. Це не обмежується лише передплатами: порівняйте strace -f -e clone,execve,write bash -c 'date'іstrace -f -e clone,execve,write bash -c 'date; true'
Жил "SO- перестаньте бути злами"

4

З Посібника з розширеного програмування Bash :

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

І трохи далі:

Msgstr "Список команд, вбудований між дужки, виконується як піддіаграма."

Приклади:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

Приклад з використанням коду ОП (з коротшим сном, бо я нетерплячий):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

Вихід:

[root@talara test]# bash sub_bash
6606
6608
6608

2
Дякую за відповідь Тім. Я не впевнений, що це повністю відповідає на моє запитання. Оскільки "Командний список, вбудований між дужки, працює як допоміжна оболонка", я б очікував, що другий sleepзапуститься в підзаголовку (можливо, в процесі підкашля, оскільки це вбудований, а не підпроцес нижньої оболонки). Однак у будь-якому випадку я б очікував існування підшаровини, тобто підпроцесу Bash під батьківським процесом Bash. Для фрагменту B вище, мабуть, це не так.
ганебний

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

@bashful я взяв на себе злом вашого коду зі своєю $BASHPIDзмінною. На жаль, те, як ви це робили, не давав вам всієї історії, яку я вірю. Дивіться мій доданий результат у відповіді.
Тім

4

Додаткова примітка до відповіді @Gilles.

За словами Жиля: The parentheses always start a subshell.

Однак номери, які мають такий підрозділ, можуть повторюватися:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

Як бачимо, $$ постійно повторюється, і це, як очікувалося, тому що (виконайте цю команду, щоб знайти правильний man bashрядок):

$ LESS=+/'^ *BASHPID' man bash

BASHPID
Розширює ідентифікатор процесу поточного процесу bash. Це відрізняється від $$ за певних обставин, таких як підпакети, які не потребують повторної ініціалізації bash.

Тобто: Якщо оболонку не повторно ініціалізувати, $$ - те саме.

Або з цим:

$ LESS=+/'^ *Special Parameters' man bash

Спеціальні параметри
$ Розширюється на ідентифікатор процесу оболонки. У підпакеті () вона розширюється на ідентифікатор процесу поточної оболонки, а не на нижню.

$$Це ідентифікатор поточної оболонки (НЕ подоболочки).


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