Цикл не работает, когда вложен?

Вопрос или проблема

Мой код ниже, как вы видите, ясен, внутренний цикл, похоже, работает, но когда он вложен в другой, он не работает, (не знаю почему)

Идея заключается в том, чтобы итерировать переменные a b с помощью while1 и передать их в while2, while2 ждет появления строки в log_file, чтобы выполнить оператор case.

Это не работает с фактическим процессом и его реальным log_file

#!/bin/bash

bi_log='/var/log/process/process.log'
bi_conf="/etc/process/process.conf"

cat input_file | while
    read a b
do
    while IFS= read -r line; do
        case $line in
            "Обработка данных завершена*"* )        echo "Найдено" &&
                                               echo $a $b >> $bi_conf
                                               ;;
        esac
        sleep 1
        break
    done < <(tail -n0 -f $bi_log)
done

Похоже, что внутренний цикл работает нормально, но, возможно, что-то не так с его вложением в внешний. Переменные a & b не выводятся в файл.

С вашим скриптом есть несколько проблем, начиная с того, что tail -n0 -f $bi_log никогда не завершится, поэтому внешний цикл никогда не дойдет до второй строки input_file.

Попробуйте что-то более похожее на это:

while read -r a b ; do
  tail -n0 -f "$bi_log" | 
    awk -v a="$a" -v b="$b" \
      '/Обработка данных завершена/ {
         print "найдено" > /dev/stderr;
         print a, b ;
         exit
       }' >> "$bi_conf"
done < input_file

Здесь скрипт awk печатает свой вывод и выходит, как только находит совпадение. Это завершает конвейер tail -n0 -f, и оболочка while продолжает на следующую строку input_file (и начинает новый конвейер tail | awk).

Кстати, -v a="$a" -v b="$b" — это способ передать переменные оболочки $a в переменную awk a и переменную оболочки $b в переменную awk b. В качестве альтернативы вы можете export a b в оболочке (чтобы они были экспортированы в окружение, доступное дочерним процессам) и затем использовать ENVIRON["a"] и ENVIRON["b"] в awk.

Если бы я знал больше о input_file и переменных a и b, вероятно, это все можно было бы сделать в awk или perl.


Кроме того, вы можете сделать это просто с bash и grep. Например:

while read -r a b ; do
  tail -n0 -f "$bi_log" | grep -q -m 1 'Обработка данных завершена'
  echo "Найдено"
  echo "$a $b" >> "$bi_conf"
done < input_file

Опция -m NUM говорит grep выйти после NUM совпадений (в данном случае, NUM=1). Опция -q говорит grep быть тихим, т.е. не выводить результат и просто возвращать код выхода (0 для успеха, 1 для ошибки… но поскольку ввод grep поступает от tail -f, он будет продолжать читать бесконечно, пока не найдет совпадение).


PS: как общее правило, если вы когда-либо окажетесь в ситуации, когда пишете цикл while/read в оболочке, вы должны остановиться и подумать: “Наверное, мне стоит сделать это в awk/perl/python/чем-то, кроме оболочки”. Затем напишите это на более подходящем языке.

Оболочка хороша для координации выполнения инструментов обработки текста и данных. Она не очень хороша в самой обработке.

Смотрите Почему использование цикла оболочки для обработки текста считается плохой практикой? для подробностей и примеров.

while2 выполняется в своем собственном подсетевом сеансе,
поэтому он не может видеть переменные a и b, определенные в while1.

Ответ или решение

Проблема с вложенным циклом в Bash

При работе с вложенными циклами в Bash, особенно когда внутренний цикл ожидает данные из потока, могут возникать некоторые сложности. В вашем коде есть несколько проблем, связанных как с логикой, так и с синтаксисом, которые могут приводить к тому, что переменные a и b не сохраняются, и внутренний цикл не выполняется так, как ожидалось.

Анализ текущего кода

Ваш исходный код выглядит следующим образом:

#!/bin/bash

bi_log='/var/log/process/process.log'
bi_conf="/etc/process/process.conf"

cat input_file | while read a b; do
    while IFS= read -r line; do
        case $line in
            "Processing Data ended*"* ) 
                echo "Found" &&
                echo $a $b >> $bi_conf
                ;;
        esac
        sleep 1
        break
    done < <(tail -n0 -f $bi_log)
done
Проблемы в коде:
  1. Поток данных из tail -f: Команда tail -n0 -f $bi_log будет бесконечно ждать появления новых строк в файле. Это означает, что внешний цикл (while read a b) никогда не сможет обработать следующую строку из input_file, так как tail продолжает свое выполнение.

  2. Локальные переменные в подсистемах: Внутний цикл, в котором вы используете while, работает в подсистеме. То есть переменные a и b, определенные в родительском цикле, не будут видны в дочернем цикле.

Предложенное решение

Для решения этих проблем я рекомендую использовать awk или grep, как вы уже заметили в обсуждении. Вот пример переделанного кода с использованием awk:

while read -r a b; do
    tail -n0 -f "$bi_log" | 
    awk -v a="$a" -v b="$b" '
    /Processing Data ended/ {
        print "Found" > "/dev/stderr";
        print a, b;
        exit
    }' >> "$bi_conf"
done < input_file
Объяснение изменений:
  • Поток данных: Я использую tail -n0 -f "$bi_log" | awk ..., что позволяет прослушивать файл логов и обрабатывать данные сразу же, когда они появляются, без создания лишней подсистемы.
  • Передача переменных: Флаг -v передает значения переменных a и b в awk, где они могут быть использованы непосредственно.
  • Автоматическое завершение: exit внутри awk завершит работу, как только будет найдено первое совпадение, что позволит завершить поток из tail.

Альтернативный подход

Также вы можете использовать grep, чтобы сделать код более простым:

while read -r a b; do
    tail -n0 -f "$bi_log" | grep -q -m 1 'Processing Data ended'
    echo "Found"
    echo "$a $b" >> "$bi_conf"
done < input_file
  • Флаги -m и -q позволяют grep завершаться после первого совпадения, что сделает выполнение более эффективным.

Заключение

Работа с вложенными циклами в Bash может быть непростой, особенно при ожидании потоковых данных. Неправильное использование конструкций, таких как tail, может привести к тому, что некоторые части кода никогда не будут выполнены. Использование более подходящих инструментов, таких как awk или grep, поможет вам избежать распространенных проблем и сделать код более читабельным и эффективным.

Оцените материал
Добавить комментарий

Капча загружается...