Ошибка bin sh not found: причины и пошаговое решение

Ошибка /bin/sh: not found чаще всего появляется при запуске контейнера Docker, выполнении shell-скрипта или сборке образа — и почти всегда означает, что система не может найти интерпретатор командной строки по указанному пути. Типичный сценарий: команда docker run завершается сообщением вроде exec /bin/sh: no such file or directory, хотя скрипт на месте и права выставлены.

Проблема обманчива: текст ошибки указывает на отсутствие sh, но реальная причина может быть совсем в другом — в невидимых символах переноса строки Windows, в образе без shell вообще или в битом симлинке. Ниже разберём, как быстро локализовать источник и исправить ситуацию без лишних действий.

Что означает ошибка /bin/sh: not found

Интерпретатор sh (Bourne Shell) — базовая оболочка Unix-подобных систем. Когда вы запускаете скрипт, указываете ENTRYPOINT в Dockerfile или выполняете команду через sh -c, система обращается к файлу /bin/sh. Если по этому пути ничего нет или файл не читается корректно, возникает рассматриваемая ошибка.

Важный нюанс: сообщение no such file or directory при запуске исполняемого файла может относиться не к самому скрипту, а к интерпретатору, указанному в его shebang (первая строка вида #!/bin/sh). Ядро читает эту строку и пытается найти интерпретатор — если путь «битый», ошибка выглядит так, будто отсутствует сам скрипт.

  • 🐳 Docker-контейнеры — образ собран на базе scratch или distroless, где shell отсутствует в принципе
  • 📄 Скрипты с CRLF — файл сохранён в Windows с окончаниями строк \r\n, и система ищет интерпретатор /bin/sh\r
  • 🔗 Битый симлинк/bin/sh является ссылкой на dash или bash, которые удалены или повреждены
  • 📦 Многоэтапная сборка — бинарник скопирован в минимальный образ, а команда запуска требует shell

Быстрая диагностика: что проверить в первую очередь

Прежде чем что-то менять, определите, где именно возникает сбой. Если ошибка появляется при запуске контейнера, посмотрите, на каком базовом образе он собран — команда docker inspect или просмотр Dockerfile даст ответ. Если сбой при выполнении локального скрипта — проверьте его первую строку и формат окончаний строк.

Полезная проверка для скрипта — вывести информацию о файле:

file script.sh

head -1 script.sh | od -c | head -2

Первая команда покажет тип файла и, что важно, упомянет CRLF line terminators, если они есть. Вторая выведет первую строку посимвольно — если после #!/bin/sh виден символ \r, причина найдена.

📊 Где вы столкнулись с ошибкой /bin/sh
not found?:При запуске Docker-контейнера
При выполнении shell-скрипта
При сборке образа (docker build)
В CI/CD пайплайне

Причина 1: окончания строк CRLF в скрипте

Самая частая и самая коварная причина. Редакторы в Windows (включая некоторые настройки VS Code и Notepad++) сохраняют файлы с окончаниями строк \r\n. Git тоже может автоматически конвертировать окончания при клонировании, если включён параметр core.autocrlf. В результате shebang превращается в #!/bin/sh\r, и ядро ищет интерпретатор с «мусорным» символом в имени.

Исправить файл можно несколькими способами. Если установлена утилита dos2unix, достаточно одной команды:

dos2unix script.sh

Без неё подойдёт sed или tr:

sed -i 's/\r$//' script.sh

или

tr -d '\r' < script.sh > script_fixed.sh

⚠️ Внимание: после исправления окончаний строк проверьте, что у скрипта сохранились права на выполнение (chmod +x script.sh). Некоторые способы пересохранения файла сбрасывают флаг исполняемости.

Причина 2: в образе контейнера нет shell

Минималистичные базовые образы — scratch, distroless, некоторые сборки на базе alpine после очистки — могут вообще не содержать /bin/sh. Это осознанный выбор: меньше компонентов — меньше поверхность атаки и размер образа. Но если в Dockerfile указано ENTRYPOINT ["/bin/sh", "-c", "..."] или CMD в shell-форме, запуск завершится ошибкой.

Проверить наличие shell в образе можно, запустив контейнер с альтернативной точкой входа (если есть хоть какой-то бинарник) или просто изучив Dockerfile. Решений несколько:

  • 🔧 Сменить базовый образ — перейти со scratch на alpine или debian-slim, где shell присутствует
  • 📋 Использовать exec-форму — писать ENTRYPOINT ["/app/mybinary"] без обёртки в shell, если бинарник самодостаточен
  • 🏗️ Скопировать shell вручную — в многоэтапной сборке перенести /bin/sh и его зависимости из build-стадии (хрупкий путь, требует учёта динамических библиотек)
  • 🔄 Переписать логику запуска — вынести shell-обвязку наружу, например в docker-compose или оркестратор

☑️ Проверка контейнера при ошибке /bin/sh

Выполнено: 0 / 5

Причина 3: повреждён или удалён /bin/sh в системе

На обычной Linux-системе /bin/sh — это, как правило, символическая ссылка. В Debian и Ubuntu она указывает на dash, в некоторых дистрибутивах — на bash. Если целевой интерпретатор удалён, переустановлен с ошибкой или ссылка перезаписана вручную, любые скрипты с shebang #!/bin/sh перестанут работать.

Диагностика занимает пару команд. Посмотрите, куда ведёт ссылка и существует ли цель:

ls -l /bin/sh

readlink -f /bin/sh

Если цель отсутствует, восстановите её штатными средствами пакетного менеджера. В Debian-подобных системах пакет dash переустанавливается через apt, в Alpine — через apk. Точные названия пакетов зависят от дистрибутива, поэтому сверяйтесь с его документацией.

⚠️ Внимание: не перенаправляйте /bin/sh на произвольный интерпретатор «для удобства». Системные скрипты могут рассчитывать именно на POSIX-совместимое поведение dash, и замена способна нарушить загрузку сервисов и работу пакетного менеджера.
Почему /bin/sh — это не всегда bash

Исторически sh — оригинальная оболочка Bourne Shell. В современных дистрибутивах /bin/sh является ссылкой на минималистичный POSIX-совместимый интерпретатор: в Debian и Ubuntu это dash, во многих других системах — bash в режиме совместимости. Поэтому скрипты с bash-специфичными конструкциями (массивы, двойные квадратные скобки, некоторые расширения) могут ломаться, если запускать их через sh. Если скрипт написан под bash, указывайте в shebang именно #!/bin/bash или #!/usr/bin/env bash.

Ошибка при сборке образа: RUN и многоэтапные сборки

Отдельный сценарий — ошибка возникает не при запуске, а на этапе docker build, на инструкции RUN. Здесь причины те же, но проявляются иначе: либо базовый образ текущей стадии не содержит shell, либо в копируемом скрипте остались CRLF-окончания. Даже один скрипт с Windows-окончаниями строк способен остановить всю сборку на шаге RUN, причём сообщение об ошибке не всегда явно указывает на виновный файл.

Порядок действий при сбое сборки:

  1. Найдите в выводе сборки шаг, на котором произошла ошибка, и соответствующую инструкцию в Dockerfile.
  2. Проверьте формат строк всех скриптов, копируемых через COPY до этого шага.
  3. Если используется многоэтапная сборка, убедитесь, что финальная стадия основана на образе с shell — либо уберите зависимость от shell.
  4. Пересоберите с флагом --no-cache, чтобы исключить влияние закэшированных слоёв.

Сравнение типичных сценариев и решений

Сценарий Вероятная причина Способ проверки Решение
Запуск локального скрипта CRLF в shebang file script.sh dos2unix или sed
docker run завершается сразу Образ без shell Анализ Dockerfile, базового образа Сменить образ или exec-форма ENTRYPOINT
Сбой на шаге RUN при build CRLF в копируемом скрипте Просмотр логов сборки Исправить файл, пересобрать с --no-cache
Скрипты не работают на хосте Битый симлинк /bin/sh ls -l /bin/sh Переустановить dash/bash
Ошибка только в CI/CD Git конвертирует EOL при checkout Проверить .gitattributes и core.autocrlf Настроить .gitattributes с eol=lf

Профилактика: как не столкнуться с ошибкой снова

Большинство повторных случаев связано с рассинхронизацией окружений: разработчик работает в Windows, CI собирает в Linux, а контейнер запускается на минимальном образе. Чтобы свести риски к минимуму, вам стоит закрепить несколько привычек на уровне проекта, а не личных настроек редактора.

Настройте репозиторий так, чтобы формат файлов контролировался автоматически: .gitattributes с принудительным eol=lf для скриптов и конфигов, линтеры вроде shellcheck в пайплайне, проверка Dockerfile на соответствие базового образа и команд запуска. Необходимо также явно документировать, какие образы команда использует как базовые и есть ли в них shell.

Часто задаваемые вопросы

Почему скрипт запускается через sh script.sh, но падает при ./script.sh?

При вызове sh script.sh вы явно указываете интерпретатор, и shebang игнорируется. При запуске через ./script.sh ядро читает первую строку файла — и если там #!/bin/sh с символом возврата каретки или путь к отсутствующему интерпретатору, возникает ошибка. Расхождение поведения — верный признак проблемы именно в shebang.

Можно ли запустить shell внутри контейнера, собранного на scratch?

Нет, если в образе физически нет бинарника shell и его зависимостей. Команда docker exec -it container sh завершится той же ошибкой. Варианты: пересобрать образ с shell, использовать отладочные ephemeral-контейнеры в Kubernetes или анализировать образ инструментами вроде docker export и просмотра файловой системы с хоста.

Чем alpine отличается от debian-slim в контексте этой ошибки?

В alpine shell есть (/bin/sh — ссылка на busybox), но система использует библиотеку musl вместо glibc, поэтому бинарники, собранные под glibc, могут не запускаться с похожей ошибкой «not found». В debian-slim используется glibc и полноценный dash. Если бинарник «не находится» в alpine, хотя файл на месте, проверьте его зависимости командой ldd.

Git сам портит окончания строк. Как это отключить?

Поведение управляется параметром core.autocrlf и файлом .gitattributes. Надёжнее всего не полагаться на локальные настройки, а зафиксировать правила в репозитории: добавьте в .gitattributes строку *.sh text eol=lf. После этого Git будет хранить и выгружать скрипты с Unix-окончаниями независимо от настроек машины разработчика.

Ошибка появляется только в CI-пайплайне, локально всё работает. Что проверить?

Начните с окружения CI: какой runner-образ используется, есть ли в нём shell, как настроен checkout кода (некоторые CI-системы применяют собственные правила конвертации EOL). Сравните формат файла локально и в CI командой file прямо в шаге пайплайна — это быстрее всего покажет расхождение.