Заголовки bash-скриптів

Будь-який bash-скрипт починається з магічного напису #!/bin/bash. Ті, хто уважно читав підручники, можуть навіть пригадати, що магічними насправді є лише символи #!, а все, що за ними — шлях до інтерпретатору. Але мало кому відомо, що в якості останньго зовсім не обов’язково використовувати одну з багатьох різновидностей sh. Залежно від змісту файлу можна вказати /bin/perl або /usr/bin/awk. А можна взагалі обійтися без вказування інтерпретатору.

В якості прикладу розглянемо такий простенький скриптик:

#!/bin/bash
awk ' 
{ print $2 }
' $*

Він виводить другу колонку з файлу, ім’я якого було передано в якості параметру. Наприклад:

% cat tfile
one two three four five six seven
one1 two1 three1 four1 five six seven
% ./sc1 tfile
two
two1
%

Але навіть такий короткий скрипт можна ще зменшити. По-перше, видалимо заголовок:

awk ' 
{ print $2 }
' $*

Ну а по-друге, оскільки всю роботу виконує awk, можна зразу вказати на нього:

#!/usr/bin/awk -f
{ print $2 }

Обидва варіанти видають той же самий результат, що й початковий скрипт. Щоб проаналізувати час виконання, кожний з них довелося запустити 100 разів. Результати:

0.80 user 0.61 system 0:01.42 elapsed 99% CPU у скрипта с заголовком #!/bin/bash

0.42 user 0.42 system 0:00.86 elapsed 97% CPU у скрипта без заголовка

0.16 user 0.26 system 0:00.43 elapsed 97% CPU при прямому виклику awk

Отже, “класичний” скрипт виконувався довше за всіх, а найшвидшим виявився скрипт з прямим викликом awk. Що ж, для того, щоб зрозуміти, чому саме так, звернемося до man bash, а саме до опису процесу виклику команди:

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

Якщо виконати команду не вдалось, тому що файл не відповідає підтримуваним форматам, і цей файл не є каталогом, тоді буде зроблено висновок, що цей файл є сценарієм командного інтерпретатору (відповідно, містить команди). Для його виконання буде запущено породжений командний інтерпретатор….

Якщо текст в файлі починається з #!, решта першого рядку задає інтепретатор для програми. Командний інтерпретатор запустить вказаний інтерпретатор у операційних системах, що безпосередньо не підтримують вказаний формат виконуваного файлу. У якості аргументів командного рядку цей інтерпретатор отримає один необов’язковий агрумент, потім назву інтерпретатору з першого рядку програми, потім назву самої програми та її аргументи (якщо такі були задані).

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

Третій випадок цікавіше. Зверніть увагу, в тексті скрипту явно не було вказано, що дані слід брати з файлу tfile. Ім’я файлу з даними буде передано при формуванні рядку виклику інтерпретатору: awk -f sc2 tfile. Ще один цікавий момент полягає у тому, що коментарі в awk-програмах теж відділяються знаком “#”, тому перший рядок файлу sc2 фактично буде проігноровано.

Для чого це можна використати? Важко сказати. Можна створити пару веселих приколів. Типу сценарій, який сам себе друкує: #!/bin/cat. Або навіть сценарій, що сам себе видаляє: #!/bin/rm -vf. До речі, інтерпретатор отримає лише один аргумент, тому #!/bin/rm --verbose --forse не спрацює (точніше, спрацює трохи нетак).

Більш реальні приклади:

  • #!/bin/bash -x рекомендується використовувати для налагодження (виведення додаткової інформації)

  • #!/bin/echo теж можна використовувати при налагодженні, щоб точно знати, з яким командним рядком викликано скрипт (хоча, для цього є інші засоби)

  • #!/bin/make -f файл збирання, який можна запустити. Хтозна, може комусь таке диво і знадобиться


Кроспост, текст там вдалося відформатувати однозначно краще...