Немного про shebang в Linux
Shebang — это последовательность #!
в начале исполняемого файла, которая указывает системе, какая программа должна интерпретировать этот скрипт📝.
Популярным шебангом для bash
является #!/bin/bash
. Его особенность в том, что он жёстко указывает на конкретный путь в файловой системе: /bin/bash
. Это справедливо, если вы точно знаете, что bash
действительно находится по этому пути🛣️.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.
Всё бы ничего, но есть нюансы🤷♂️. Первый - в большинстве Linux дистрибутивов bash
установлен в /usr/bin/bash
, что уже может завершить ваш скрипт ошибкой. Чтобы этого избежать, в системе создают символьную ссылку /bin
, указывающую на директорию /usr/bin
:
ls -ld /bin
Permissions Size User Group Date Modified Name
lrwxrwxrwx 7 root root 2023-09-22 19:26 /bin -> usr/bin
Вывод напрашивается сам собой: в качестве shebang указывать /usr/bin/bash
, но и это не является универсальным решением🤔. В некоторых дистрибутивах бинарник bash
может все таки находиться в другом месте.
Поэтому часто в качестве shebang используют #!/usr/bin/env bash
💡.
env
- утилита для работы с переменными окружения. В случае с bash
утилита env
перед запуском ищет бинарник в переменной $PATH
, которая содержит список директорий с исполняемыми файлами, разделённых символом :
. Например:
/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
Такой вариант шебанга является более универсальным: если bash
установлен в нестандартном месте, но доступен через PATH
, он будет найден.
Данное поведение одинаково в Linux, macOS, BSD и виртуальных окружениях😌.
Но есть и недостатки😠. Данный способ запуска чуть менее эффективен (есть лишний вызов env
), и в редких ситуациях может запуститься «не тот» bash
, если в PATH
находятся разные его версии. Например, есть два разных баша: /usr/local/bin/bash
и /usr/bin/bash
. В случае PATH
, указанного выше, будет использован /usr/local/bin/bash
.
Увидеть разницу в работе шебангов можно с помощью инструмента отслеживания системных вызовов strace
, он же “страус”🐓.
Создадим два скрипта и сделаем их исполняемыми:
cat > test-bin.sh <<EOF
#!/bin/bash
echo "I am /bin/bash"
EOF
cat > test-env.sh <<EOF
#!/usr/bin/env bash
echo "I am env bash"
EOF
chmod +x test-bin.sh test-env.sh
Запускаем первый с помощью страуса и фильтруем вывод по вызову execve
:
strace -f -e trace=execve -s 200 ./test-bin.sh
Видим лишь один вызов:
execve("./test-bin.sh", ["./test-bin.sh"], 0x7ffe817c2458 /* 37 vars */) = 0
I am /bin/bash
+++ exited with 0 +++
Теперь запустим вариант с env
:
strace -f -e trace=execve -s 200 ./test-env.sh
Тут вывод будет заметно длиннее. Видно, что происходит поиск исполняемого файла в нескольких директориях:
strace -f -e trace=execve -s 200 ./test-env.sh 2>&1
execve("./test-env.sh", ["./test-env.sh"], 0x7ffd9fe2e898 /* 37 vars */) = 0
execve("/usr/local/sbin/bash", ["bash", "./test-env.sh"], 0x7ffcaca77068 /* 37 vars */) = -1 ENOENT (No such file or directory)
execve("/usr/local/bin/bash", ["bash", "./test-env.sh"], 0x7ffcaca77068 /* 37 vars */) = -1 ENOENT (No such file or directory)
execve("/usr/sbin/bash", ["bash", "./test-env.sh"], 0x7ffcaca77068 /* 37 vars */) = -1 ENOENT (No such file or directory)
execve("/usr/bin/bash", ["bash", "./test-env.sh"], 0x7ffcaca77068 /* 37 vars */) = 0
I am env bash
+++ exited with 0 +++
Любопытно, что порядок поиска не полностью соответствует очерёдности директорий в PATH
. Чередуясь проверяются *sbin
, затем *bin
🤔.
Тем не менее, рекомендуется использовать именно #!/usr/bin/env bash
для большей универсальности👍.
Стоит отметить, что подобный подход справедлив для любых интерпретаторов, например #!/usr/bin/env python
, #!/usr/bin/env awk
и т.д.