Shebang — это последовательность #! в начале исполняемого файла, которая указывает системе, какая программа должна интерпретировать этот скрипт📝.
Популярным шебангом для bash является #!/bin/bash. Его особенность в том, что он жёстко указывает на конкретный путь в файловой системе: /bin/bash. Это справедливо, если вы точно знаете, что bash действительно находится по этому пути🛣️.
🖐️Эй!
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.
Всё бы ничего, но есть нюансы🤷♂️. Первый - в большинстве Linux дистрибутивов bash установлен в /usr/bin/bash, что уже может завершить ваш скрипт ошибкой. Чтобы этого избежать, в системе создают символьную ссылку /bin, указывающую на директорию /usr/bin:
ls -ld /binPermissions 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 и т.д.


