Шаги оболочки: sh, bat, powershell и коды возврата

Рейтинг: 56.5% · 9 голосов
Подробный курс по Jenkins и CI/CD: установка и настройка, плагины и Configuration as Code, конвейеры (pipeline) и Jenkinsfile, declarative и scripted, агенты, Docker и Kubernetes, общие библиотеки на Groovy, безопасность и credentials, интеграции (SonarQube, артефакты), траблшутинг. Актуально на 2026.
Ответить
Аватара пользователя
Andrey_CICD
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Шаги оболочки: sh, bat, powershell и коды возврата

Сообщение Andrey_CICD »

Оглавление курса (47)
  1. Что такое Jenkins и зачем нужен CI/CD
  2. Jenkins в 2026: версии, экосистема и сравнение с GitLab CI и GitHub Actions
  3. Архитектура Jenkins: контроллер, агенты, узлы и исполнители
  4. Установка Jenkins: пакеты, Docker и Kubernetes
  5. Первичная настройка Jenkins: мастер, плагины, пользователи
  6. Плагины Jenkins: установка, обновление и ключевой набор
  7. Configuration as Code (JCasC): настройка Jenkins декларативно
  8. Конвейеры Jenkins и Jenkinsfile: pipeline as code
  9. Scripted против Declarative: два синтаксиса конвейера
  10. Структура конвейера: node, stage, steps и первый pipeline
  11. Генератор сниппетов, запуск конвейера и Replay
  12. Типы заданий Jenkins: Freestyle, Pipeline, Multibranch и Organization
  13. Декларативный конвейер: структура pipeline, agent, stages
  14. agent, environment и переменные в конвейере
  15. tools, options, parameters и triggers в декларативном конвейере
  16. stages, parallel и matrix: организация этапов
  17. Условное выполнение when и блок post
  18. Триггеры сборки: webhook от GitHub, SCM polling и расписание
  19. Параметры сборки и пользовательский ввод
  20. Управление потоком: timeout, retry, sleep и waitUntil
  21. Параллелизм и блокировки: parallel, lock, milestone
  22. Постобработка и уведомления о результате сборки
  23. Groovy для Jenkins: основы скриптового конвейера
  24. Скриптовый конвейер: логика, циклы и функции
  25. Общие библиотеки Jenkins: Shared Libraries
  26. Подключение и загрузка общих библиотек
  27. Шаги оболочки: sh, bat, powershell и коды возврата (вы здесь)
  28. Переменные среды, withEnv и рабочие пространства
  29. Работа с файлами, артефактами и отпечатками
  30. Сборка проектов в конвейере: Maven, Gradle, npm
  31. Качество кода: SonarQube и покрытие тестами
  32. Управление артефактами: публикация в Nexus и Artifactory
  33. Docker в Jenkins: образы как агенты сборки
  34. Сборка и публикация Docker-образов из конвейера
  35. Jenkins в Kubernetes: динамические pod-агенты
  36. Развёртывание в Kubernetes из конвейера
  37. Безопасность Jenkins: аутентификация и матрица прав
  38. Учётные данные Jenkins: credentials в конвейере
  39. Безопасность сценариев: Groovy sandbox и Vault
  40. Уведомления и отчёты: email, Slack, Telegram, HTML
  41. Blue Ocean и интерфейс Jenkins в 2026
  42. Автоматизация Jenkins: CLI, REST API и script console
  43. Поиск и устранение неисправностей: читаем логи конвейера
  44. Типичные ошибки конвейера: сериализация и неутверждённый код
  45. Эксплуатация Jenkins: бэкап, обновление, масштабирование
  46. Конвертация и миграция: от Freestyle к Jenkinsfile
  47. Итог: сквозной CI/CD конвейер и Jenkins против альтернатив в 2026
Рано или поздно весь конвейер упирается в одну простую вещь - надо выполнить команду операционной системы. Собрать проект, запустить тесты, скопировать артефакт, дёрнуть скрипт деплоя. Сам Jenkins почти ничего не "делает" руками - он оркестрирует, а реальную работу выполняет оболочка на агенте. Поэтому шаг jenkins sh (и его собратья bat и powershell) - это рабочая лошадка любого Jenkinsfile. И именно тут новичков ждёт горка граблей: то конвейер падает на пустом месте, то вывод команды теряется, то Groovy подставил переменную не туда. Разберёмся, как это устроено на самом деле, на стабильной ветке Jenkins LTS 2.541.x (Java 17/21) образца 2026 года.

Три шага под три платформы: sh, bat, powershell

Выбор шага зависит от того, на каком агенте крутится этап. На Linux и macOS используют sh - он запускает команду через POSIX-оболочку. На Windows для классической командной строки cmd есть bat, а для PowerShell - шаг powershell (или pwsh для кросс-платформенного PowerShell Core). Логика jenkins shell везде одинаковая: ты передаёшь строку со скриптом, Jenkins пишет её во временный файл на агенте, запускает и следит за кодом возврата.

Простейший declarative-конвейер, сборка 98, выглядит так:

Код: Выделить всё

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'echo "Собираю проект на агенте $NODE_NAME"'
                sh 'make build'
            }
        }
    }
}
На Windows-агенте тот же замысел через jenkins bat:

Код: Выделить всё

steps {
    bat 'echo Building on %COMPUTERNAME%'
    powershell 'Get-ChildItem -Path . -Filter *.dll | Measure-Object'
}
Важный момент: bat по умолчанию ведёт себя как пакетный файл, и код возврата он берёт из ERRORLEVEL последней команды. PowerShell же падает не по любой ошибке, а по непойманным исключениям и по $LASTEXITCODE - об этом ниже.

Изображение

Коды возврата: почему jenkins sh падает по умолчанию (и как это поведение менять)

Ключевое правило: шаг оболочки считается успешным только если процесс вернул нулевой код. Любой ненулевой exit code - и шаг бросает исключение, этап краснеет, конвейер останавливается. Это правильное поведение по умолчанию: если make build вернул 2, продолжать сборку бессмысленно.

Но иногда ненулевой код - это нормально. Например, grep возвращает 1, когда ничего не нашёл, а ты как раз хочешь проверить "есть ли в логе слово ERROR". Тогда падать не надо. Для таких случаев у шага есть параметр returnStatus:

Код: Выделить всё

script {
    def code = sh(script: 'grep -q ERROR build.log', returnStatus: true)
    if (code == 0) {
        echo 'Нашёл ERROR в логе сборки'
    } else {
        echo 'Лог чистый, идём дальше'
    }
}
Обрати внимание на синтаксис: как только тебе нужно вернуть значение, ты обязан использовать форму с круглыми скобками и именованным параметром script. Голая строка sh 'команда' значение не возвращает. И ещё - формы returnStatus и jenkins sh returnStdout в одном вызове несовместимы, выбирай что-то одно.

Чтобы забрать сам вывод команды в переменную Groovy, нужен returnStdout. Это типичный паттерн "получить git-хеш / версию / список файлов":

Код: Выделить всё

script {
    def commit = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
    def branch = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
    echo "Собираю ${branch} на коммите ${commit}"
}
Метод trim() тут не косметика, а необходимость: к выводу всегда прилипает завершающий перевод строки, и без trim() ты получишь "abc123\n", что сломает имя тега или сравнение строк. Это первая по частоте ошибка при работе с jenkins sh returnStdout.

Многострочные скрипты, set -e и кавычки

Однострочниками реальная jenkins сборка не ограничивается. Многострочный скрипт оформляют тройными кавычками:

Код: Выделить всё

sh '''
    set -euo pipefail
    echo "Готовлю окружение"
    ./configure --prefix=/opt/app
    make -j$(nproc)
    make install DESTDIR=$WORKSPACE/out
'''
Тут критичный нюанс. Jenkins запускает скрипт примерно как sh -xe, поэтому каждая команда уже под флагом -e, и при первой же ошибке скрипт прервётся. Но это не покрывает пайпы и неинициализированные переменные. Поэтому хороший тон - в начале явно ставить set -euo pipefail: тогда упадёт и команда внутри пайплайна (a | b), и обращение к незаданной переменной. Без pipefail конструкция вроде curl ... | tar xz вернёт код от tar, и провал curl ты просто не заметишь.

Теперь про кавычки и интерполяцию - это главные грабли всего урока. В Jenkinsfile сосуществуют два мира подстановки переменных, и их легко перепутать:
  • Groovy-интерполяция ${ИМЯ} работает только внутри двойных кавычек ". Jenkins подставит значение ДО того, как скрипт уйдёт в оболочку.
  • Shell-переменная $ИМЯ раскрывается уже самой оболочкой на агенте, в момент выполнения.
Сравни два вызова:

Код: Выделить всё

script {
    def appVersion = '2.4.1'
    // Groovy подставит версию ещё до запуска оболочки:
    sh "docker build -t myapp:${appVersion} ."
    // А тут $BUILD_NUMBER раскроет сама оболочка из переменной окружения:
    sh 'echo "Это сборка номер $BUILD_NUMBER"'
}
Грабли смешивания Groovy- и shell-переменных

Самая коварная ошибка - использовать двойные кавычки там, где переменную должна разворачивать оболочка, а не Groovy. Допустим, ты пишешь:

Код: Выделить всё

// ОПАСНО: Groovy первым доберётся до ${HOME}
sh "cp build/app.tar.gz ${HOME}/releases/"
Если переменной HOME в Groovy-окружении нет, конвейер упадёт с groovy.lang.MissingPropertyException ещё до запуска оболочки. А даже когда не падает - подстановка секретов через двойные кавычки светит их в логе и в истории шага. Правильно для shell-переменных брать одинарные кавычки, чтобы строку до оболочки никто не трогал:

Код: Выделить всё

// Безопасно: $HOME раскроет сама оболочка на агенте
sh 'cp build/app.tar.gz $HOME/releases/'
Простое правило, которое экономит часы: значение известно в Groovy (параметр сборки, результат предыдущего шага) - двойные кавычки и ${}. Значение - это переменная окружения агента или секрет - одинарные кавычки и $VAR, пусть разворачивает оболочка. Для секретов это ещё и вопрос безопасности: связка с withCredentials кладёт значение в окружение, и его нужно читать как $TOKEN внутри одинарных кавычек, иначе токен утечёт в консоль.

Когда sh, а когда специализированный шаг

Соблазн сделать всё через jenkins shell велик, но не всегда оправдан. Голый sh 'git clone ...' проигрывает шагу checkout / git, потому что не умеет нормально работать с учётками, ретраями и сабмодулями. sh 'docker build' можно заменить на методы Docker Pipeline. Архивацию артефактов делает archiveArtifacts, а не sh 'cp' в случайную папку. Плюс специализированные шаги переносимы между Linux и Windows, а sh - нет.

Эмпирика 2026 года: если для задачи есть зрелый шаг плагина - бери его, получишь обработку ошибок, логи и кросс-платформенность даром. sh оставляй для того, чего плагин не покрывает: твои билд-скрипты, утилиты, разовая логика. И помни про эфемерных Kubernetes-агентов - в pod-шаблоне у каждого контейнера своя оболочка, и sh внутри блока container('maven') пойдёт именно в тот контейнер. Это, кстати, выгодно отличает Jenkins от линейных шагов run в GitLab CI и GitHub Actions: тут ты явно маршрутизируешь команду в нужное окружение.

Мини-лаба

Собери конвейер и прогони руками:
  • Сделай stage, где sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() кладёт хеш в переменную, и выведи её через echo.
  • Добавь шаг с returnStatus: true на команду, которая заведомо вернёт ненулевой код (например exit 3), и убедись, что конвейер НЕ упал, а ты разобрал код в if.
  • Напиши многострочный sh с set -euo pipefail и сломай одну команду внутри - посмотри, как падает этап.
  • Сделай два echo: один с Groovy-переменной в двойных кавычках ${ver}, другой с $BUILD_NUMBER в одинарных. Поменяй кавычки местами и поймай разницу в выводе.
Контрольные вопросы
  • Чем отличается поведение sh с returnStatus: true от обычного sh при ненулевом коде возврата?
  • Зачем после sh(..., returnStdout: true) почти всегда вызывают .trim()?
  • В каких кавычках писать переменную, которую должна развернуть оболочка агента, а в каких - ту, что известна Groovy, и почему это вопрос безопасности?
  • Почему checkout или archiveArtifacts часто лучше, чем sh 'git clone' и sh 'cp'?
Итог

Шаги sh, bat и powershell - это мост между конвейером и реальной работой на агенте. Запомни три якоря: ненулевой код по умолчанию валит шаг (и это хорошо), returnStatus и returnStdout дают тебе код и вывод соответственно, а кавычки решают, кто разворачивает переменную - Groovy или оболочка. Освой это - и большая часть загадочных падений Jenkinsfile перестанет быть загадкой.
👍2 ❤️2 🔥 😄 🤔
Аватара пользователя
terraform123
Сообщения: 1
Зарегистрирован: 20 май 2026, 06:00

Re: Шаги оболочки: sh, bat, powershell и коды возврата

Сообщение terraform123 »

Спасибо, наконец дошло почему мой sh падал на grep когда ничего не находил. Добавил returnStatus true и разбираю код руками, всё заработало.
👍1 ❤️ 🔥 😄 🤔1
Аватара пользователя
basedbteam
Сообщения: 1
Зарегистрирован: 31 май 2026, 21:06

Re: Шаги оболочки: sh, bat, powershell и коды возврата

Сообщение basedbteam »

Поймал ровно эти грабли с кавычками: писал секрет в двойных и он засветился в логе консоли. Переписал на одинарные с $TOKEN внутри withCredentials, теперь чисто.
👍3 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Подключение и загрузка общих библиотек
Следующая глава →
Переменные среды, withEnv и рабочие пространства

Все главы курса «Jenkins: конвейеры CI/CD от основ до продакшена»

Поделиться темой: ✈ Telegram VK
  • Похожие темы

Вернуться в «Jenkins: конвейеры CI/CD от основ до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость