Вопрос или проблема
Мой код ниже, как вы видите, ясен, внутренний цикл, похоже, работает, но когда он вложен в другой, он не работает, (не знаю почему
)
Идея заключается в том, чтобы итерировать переменные 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
Проблемы в коде:
-
Поток данных из
tail -f
: Командаtail -n0 -f $bi_log
будет бесконечно ждать появления новых строк в файле. Это означает, что внешний цикл (while read a b
) никогда не сможет обработать следующую строку изinput_file
, так какtail
продолжает свое выполнение. -
Локальные переменные в подсистемах: Внутний цикл, в котором вы используете
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
, поможет вам избежать распространенных проблем и сделать код более читабельным и эффективным.