linux scripting

sh

POSIX-совместимая командная оболочка — переносимый интерпретатор стандарта IEEE POSIX.1

Описание

sh — стандартная командная оболочка POSIX (IEEE Std 1003.1), определённая как минимальный обязательный интерфейс командной строки для совместимых Unix-систем. Исторически sh восходит к оболочке Стивена Борна (Bourne Shell, 1979), реализованной в System 7 Unix. В современных Linux-системах /bin/sh является симлинком на dash (Debian Almquist Shell) — быстрый и легковесный интерпретатор, строго следующий стандарту POSIX.

Основное назначение sh — написание переносимых скриптов, которые работают на любой POSIX-совместимой системе: Linux, macOS, BSD, Solaris, AIX. Скрипты инициализации системы (/etc/init.d/), пакетные скрипты Debian (maintainer scripts), и Makefile-цели часто используют sh намеренно — чтобы не зависеть от конкретной оболочки.

Используй sh когда переносимость важнее удобства: системные скрипты, Docker-образы с минимальным окружением (где bash может не быть), CI/CD-пайплайны, Makefile, пакетные скрипты. Избегай bash-специфичных возможностей (массивы, [[, local в функциях, brace expansion) — всё это не POSIX.

Синтаксис

sh [OPTIONS] [SCRIPT] [ARGUMENTS...] sh [OPTIONS] -c "COMMAND_STRING" [ARG0 [ARG1...]] sh [OPTIONS] -s [ARG...]

Флаги и опции

ФлагОписание
-c "cmd"Выполнить строку cmd как команду. Пример: sh -c "echo \$PATH"
-sЧитать команды из stdin. Позиционные параметры задаются через аргументы командной строки
-eЗавершить скрипт при первой ошибке (ненулевой код завершения). POSIX-эквивалент bash -e
-uТрактовать обращение к неустановленной переменной как ошибку (POSIX nounset)
-xРежим трассировки: печатать каждую команду перед выполнением с префиксом +
-nПроверить синтаксис без выполнения (noexec / dry run)
-vVerbose: печатать каждую строку скрипта по мере чтения, до расширения переменных
-fОтключить glob-подстановку (filename generation). Символы *, ?, [ воспринимаются буквально
-aАвтоматически экспортировать все переменные в окружение дочерних процессов (allexport)
-iЗапустить как интерактивную оболочку (игнорировать некоторые сигналы, показывать prompt)
-mВключить job control (monitor mode) — управление фоновыми процессами
--Конец опций. Следующий аргумент воспринимается как имя скрипта, даже если начинается с -

Паттерны использования

Переносимый POSIX-скрипт

#!/bin/sh
# Переносимый shebang — POSIX sh, не bash

set -eu

NAME="${1:-World}"
echo "Hello, ${NAME}!"

# POSIX: [ ] вместо bash [[ ]]
if [ -f /etc/os-release ]; then
  . /etc/os-release
  echo "OS: ${NAME}"
fi

Функции в POSIX sh

#!/bin/sh
set -eu

# Объявление функции (POSIX-синтаксис)
log() {
  printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*"
}

die() {
  log "ERROR: $*" >&2
  exit 1
}

log "Script started"
[ -d /tmp ] || die "/tmp not found"
log "Done"

Передача аргументов в Docker CMD

# Dockerfile: sh -c для передачи переменных окружения
CMD ["/bin/sh", "-c", "exec myapp --port $PORT"]

# Или в entrypoint-скрипте:
#!/bin/sh
set -e
exec "$@"

# В Makefile (sh по умолчанию)
deploy:
	sh scripts/deploy.sh $(ENV)

Проверка переменных и значения по умолчанию

#!/bin/sh
# POSIX parameter expansion

# Значение по умолчанию если не задана
PORT="${PORT:-8080}"

# Ошибка если не задана
: "${DATABASE_URL:?DATABASE_URL must be set}"

# Присвоить и вернуть если пусто
TMPDIR="${TMPDIR:=/tmp}"

echo "Port: $PORT"
echo "TMP: $TMPDIR"

Проверка синтаксиса скрипта

# Проверить скрипт (sh версия)
sh -n script.sh

# Трассировка выполнения
sh -x script.sh arg1 arg2

# Комбинация: verbose + trace
sh -vx script.sh

# Запуск с переопределением переменной
PORT=9090 sh script.sh

Heredoc и многострочный ввод

#!/bin/sh
# Heredoc — POSIX-совместим
cat <<'EOF'
This is a literal heredoc.
Variables like $HOME are not expanded.
EOF

cat <
        

Советы и предупреждения

Совет: проверяй POSIX-совместимость через dash
На Ubuntu/Debian /bin/sh — это dash, строго следующий POSIX. Тестируй переносимые скрипты явно через dash script.sh, чтобы убедиться в отсутствии bash-измов. На macOS /bin/sh — ограниченная версия bash, что может маскировать несовместимость.
Совет: используй printf вместо echo
Поведение echo с escape-последовательностями (\n, \t) различается между оболочками и платформами. Для переносимости всегда используй printf '%s\n' "$var" — это POSIX-стандарт с предсказуемым поведением.
Внимание: local не является POSIX
Ключевое слово local для локальных переменных в функциях — не часть POSIX, хотя поддерживается большинством оболочек на практике. Для строго переносимого кода используй субоболочки: (subshell_var=value; cmd). Также не являются POSIX: массивы, [[, $(( )) расширенная арифметика, source (используй .).
Внимание: не используй /bin/sh для bash-скриптов
Если скрипт использует bash-специфичные возможности (ассоциативные массивы, [[ ]], mapfile, brace expansion {a..z}), указывай shebang #!/usr/bin/env bash. Запуск bash-скрипта через sh на системах где sh=dash приводит к непредсказуемым ошибкам.