grep вернул 1: как отличить отсутствие совпадений от поломки проверки

Проверка запрещённых записей должна остановить выпуск, если в отчёте осталось слово FAILED. Когда записей нет, выпуск разрешён. Но скрипт с set -e завершается именно на чистом отчёте: grep возвращает 1, если не нашёл ни одной строки. При другом варианте кода разработчик добавляет || true и случайно разрешает выпуск даже тогда, когда отчёт вообще отсутствует.

Нужны три результата. Ноль означает совпадение, единица — совпадений нет, остальные статусы требуют диагностики. Для GNU grep типичная ошибка имеет статус 2; переносимый обработчик должен считать ошибкой любой статус, кроме 0 и 1. Тогда проблемы доступа и отсутствующий файл не превратятся в успешное прохождение проверки.

Сохраним статус в правильной ветке

Пример использует отдельный report.txt. В нём сначала есть только успешные записи. Замена содержимого на строку FAILED должна изменить решение проверки.

#!/bin/sh
printf '%s\n' 'OK import' 'OK validate' > report.txt
if grep -F -e 'FAILED' -- report.txt > matches.txt; then
    printf '%s\n' 'В отчёте найдены ошибки' >&2
    exit 1
else
    status=$?
    case "$status" in
        1) printf '%s\n' 'Записей FAILED нет' ;;
        *) printf 'Проверка отчёта не выполнена: grep=%s\n' "$status" >&2
           exit "$status" ;;
    esac
fi

В else первая операция сохраняет $?. Если сначала вывести сообщение или выполнить другую команду, переменная получит уже её статус. В условии if ненулевой результат grep ожидаем и разбираем явно; наличие set -e не заменяет эту логику.

Имена файлов здесь фиксированы для воспроизводимого упражнения. В рабочем скрипте путь к отчёту передают параметром и проверяют, что это именно отчёт нужного запуска. Успешное чтение старого файла тоже способно дать ложное разрешение выпуска, хотя grep выполнился правильно.

Почему не достаточно отрицания

Запись if ! grep ...; then удобна, когда интересует только логическое отсутствие совпадений. Но внутри ветки $? уже соответствует результату отрицания. Он не сохраняет исходную причину неуспеха grep. Для трёх исходов обычный if с else и немедленным сохранением статуса читается проще.

Не стоит превращать любой неуспех grep в пустой результат через || true. Такая конструкция допустима лишь тогда, когда вызывающий код действительно готов проигнорировать и отсутствие совпадений, и ошибку обработки. Для проверки допуска к выпуску это редко разумная политика.

В примере намеренно нет -q. Этот флаг завершает GNU grep после найденного совпадения; при нескольких входных файлах найденная строка способна скрыть ошибку другого файла в итоговом статусе. Если вам важна полнота чтения всех источников, сохраняйте найденные строки в файл либо направляйте stdout в /dev/null без -q. Для одного корректно открытого файла и простого ответа «встречалось ли значение» -q обычно удобен.

Испытаем все три исхода

Сначала выполните код как есть: должно появиться сообщение об отсутствии FAILED, а статус скрипта будет нулевым. Затем положите FAILED в report.txt и запустите только проверку: статус выпуска станет 1, а matches.txt сохранит найденную строку. Наконец, укажите несуществующий входной путь. Сообщение grep и ветка ошибки должны показать проблему чтения; её нельзя назвать чистым отчётом.

Такую модель удобно переносить на другие утилиты сравнения и поиска: код 1 может обозначать содержательный результат, а не поломку. При автоматизации сначала определяют контракт команды, затем связывают его с решением задания.

В курсе Linux и Bash для DevOps-инженеров проверка ошибок входит в программу. Откройте программу, чтобы перейти от отдельного условия к скриптам проверки состояния и резервного копирования, где потерянный статус меняет решение всей процедуры.

Ещё по теме

Продолжить на практике

  • Linux и Bash для DevOps-инженеров ↗

    Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.