Пустая колонка TSV сдвинула значение: выбираем разделитель awk
Экспорт содержит три колонки: идентификатор, необязательный владелец и статус. Скрипт читает $3, ожидая статус, но у части строк получает пустоту. При просмотре в редакторе таблица кажется ровной. В строках без владельца стоят две соседние табуляции, а awk с настройками по умолчанию воспринимает последовательности пробельных символов как один разделитель.
В таком режиме пустой владелец исчезает, статус становится вторым полем, и смысл колонок меняется. Нужно явно указать, что разделителем является один символ табуляции. Тогда соседние разделители создадут пустое поле, а не сольются в общий промежуток.
Воспроизведём на контролируемом входе
printf создаёт настоящий символ табуляции через \t. Это надёжнее, чем копировать визуальные пробелы из редактора. Для данных используется отдельный файл jobs.tsv.
printf 'id\towner\tstatus\na1\t\tready\na2\tteam-b\tblocked\n' > jobs.tsv
awk 'BEGIN { FS="\t"; OFS="\t" }
NR == 1 { next }
NF != 3 { bad=1; next }
$3 == "ready" { print $1, ($2 == "" ? "unassigned" : $2) }
END { if (bad) exit 2 }
' jobs.tsv
Результат — a1 и unassigned, разделённые табуляцией. У строки a1 три поля, хотя среднее пустое. Для a2 статус blocked, поэтому она не проходит фильтр. OFS задаёт разделитель при печати нескольких выражений и помогает сохранить табличный контракт результата.
Здесь первая строка считается заголовком по контракту. Если экспорт иногда приходит без заголовка, правило NR == 1 безвозвратно потеряет первую запись. Лучше договориться об одном варианте экспорта либо проверить, что первая строка действительно содержит ожидаемые имена полей. Одна строка проверки дешевле расследования пропавшей задачи.
Пустое поле не равно отсутствующей колонке
У корректной строки a1<TAB><TAB>ready пустое значение owner допустимо, но колонка существует. У строки a1<TAB>ready всего два поля. Она нарушает схему. Проверка NF != 3 отделяет эти ситуации и завершает весь запуск с ненулевым статусом, если встретилась повреждённая запись.
В примере результаты корректных строк могут быть напечатаны до обнаружения ошибки ниже. Поэтому автоматический потребитель должен проверять код завершения до использования всего результата. Для конвейера публикации безопаснее сначала писать во временный кандидат, а затем принимать его только при успешной проверке. Это сохраняет простоту потоковой обработки без предположения, что любое появление stdout уже означает успех.
Подставленное слово unassigned — политика отчёта. Не заменяйте пустоту на такой текст в исходном наборе, если это значение может быть настоящим именем команды. В машинном формате обычно удобнее сохранить пустое поле и отдельно передать признак назначения; для человекочитаемого отчёта явная подпись полезна.
Когда табуляции недостаточно
Описанный формат предполагает одну запись на строку и отсутствие табуляций внутри значений. Если система экспорта разрешает кавычки, экранированные табуляции или многострочные поля, обычный FS не реализует эти правила. Понадобится парсер конкретного формата либо иной режим экспорта. Не называйте любой текст с разделителями TSV, пока не определили, может ли разделитель находиться внутри поля.
С CRLF возможна ещё одна проблема: в последнем поле останется символ CR, и сравнение со строкой ready не сработает. Исправляйте окончания строк на границе импорта и проверяйте формат экспорта целиком. Случайное удаление CR во всех значениях способно испортить данные, где он имеет содержательный смысл.
В курсе Linux и Bash для DevOps-инженеров автоматизация повторяемых операций опирается на корректную обработку входа и ошибок. Откройте программу и продолжите к скрипту импорта, который отличает допустимое пустое значение от повреждённой строки и передаёт дальше только проверенный результат.
Ещё по теме
Продолжить на практике
- Linux и Bash для DevOps-инженеров ↗
Linux для работы с серверами: процессы, права, systemd, сеть, диски и логи. Автоматизируйте повседневные задачи на Bash и разберите типичные сбои сервиса — от занятого порта до переполненного диска.
