Цикл Bash обработал одну строку: дочерняя команда забрала stdin
Скрипт читает список серверов, запускает команду для первого и сразу завершает цикл. Ошибок нет, файл содержит ещё десять строк. В подобном сценарии стоит проверить, кто ещё читает стандартный ввод внутри тела while.
Цикл и запущенные из него команды по умолчанию наследуют один stdin. Если дочерняя программа читает его, она может забрать оставшийся список. Это встречается с ssh и с любой утилитой, которая ожидает входной поток. Проблема воспроизводится без сети обычным cat.
Дадим списку отдельный дескриптор
В следующем примере чтение списка идёт через дескриптор 3. Дочерний cat получает /dev/null вместо stdin и закрытый дескриптор 3. Его действие здесь только моделирует программу, которая могла бы забрать вход: никаких подключений к серверам нет.
#!/usr/bin/env bash
count=0
while IFS= read -r -u 3 host || [[ -n "$host" ]]; do
[[ -z "$host" ]] && continue
cat </dev/null 3<&- >/dev/null
printf 'processed: %s
' "$host"
count=$((count + 1))
done 3<<'HOSTS'
alpha
beta
gamma
HOSTS
printf 'total=%s
' "$count"
Все три имени появятся в выводе, итог равен трём. read -u 3 явно выбирает источник, а перенаправление 3<< после done открывает его для всего цикла. Для настоящего списка замените here-document на 3< "$hosts_file". Дескриптор закрывается после завершения составной команды.
Дополнение к условию read сохраняет последнюю непустую запись без финального LF. Пустые строки пропускаются отдельно. Значение host не разделяется по пробелам: IFS= и кавычки сохраняют его как одну запись.
Отдельно решите, нужен ли дочерней команде ввод
Если программа действительно должна получить текст, передайте ей собственный here-document или файл через stdin. Запрет чтения /dev/null подходит только команде, которой ввод не нужен. Для ssh параметр -n перенаправляет stdin из /dev/null; когда удалённый процесс должен получать данные, удобнее оставить список на отдельном дескрипторе и явно подать полезный поток.
Не пытайтесь лечить такую потерю sleep после каждой итерации. Оставшиеся байты уже прочитаны другой программой, задержка их не вернёт. Так же не поможет замена read на for с подстановкой cat: она внесёт разбиение записей по пробелам и новые ошибки имён.
Как найти виновника в большом скрипте
Сначала замените действие в теле на printf. Если список снова обходится полностью, по очереди возвращайте команды. Последняя возвращённая команда перед сокращением вывода — кандидат на чтение stdin. Посмотрите её справку и перенаправления, затем задайте собственный источник ввода.
В проверку включите три записи, имя с пробелом, пустую строку и последнюю строку без LF. Считайте результат после цикла. Это отличает потерю входа от другой распространённой ошибки — потери переменной в подоболочке конвейера. Здесь цикл остаётся в текущей оболочке, а исправление касается именно владения потоком.
На курсе Bash Linux + Git вы отработаете перенаправления, процессы, циклы и функции в скриптах. Откройте программу и научитесь управлять входом каждой команды, прежде чем объединять их в автоматизацию.
Продолжить на практике
- Bash Linux + Git: от новичка до уверенного пользователя ↗
Освоим два важнейших инструмента разработчика: применение bash-скриптов в практических задачах и git в его самых разных проявлениях.
