Бэш-скрипт: чтение текстовых файлов с помощью условий "AND" с разными строками

Упрощенная причина - наличие одного символа: пробел .

Раскрытие фигурных скобок не обрабатывает (не заключенные в кавычки) пробелы.

Список {...} требует (не заключенных в кавычки) пробелов.

Более подробный ответ: как оболочка анализирует командную строку .


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

  1. Разбивает команду на токены, разделенные фиксированный набор метасимволов: SPACE, TAB, NEWLINE,;, (,), , | и &. Типы токенов включают слова, ключевые слова, перенаправители ввода-вывода и точки с запятой.

Мета-символы: пробел табуляция введите ; , > | и & .

После разделения слова могут иметь тип (как понимается оболочкой):

  • Предварительные присвоения команд: LC = ALL ...
  • Команда LC = ALL echo
  • Аргументы LC = ВСЕ echo "hello"
  • Перенаправление LC = ALL echo "hello"> & 2

Раскрытие фигурных скобок

Только если "фигурная скобка" (без пробелов и метасимволов ) является одним словом (как описано выше) и не цитируется , это кандидат на «расширение скобок». Позже будут выполнены дополнительные проверки внутренней структуры.

Таким образом, это: {ls, -l} квалифицируется как «расширение скобок» и становится ls -l , либо как первое слово или ] аргумент (в bash zsh отличается).

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

Но этого не произойдет: {ls, -l} .Bash разделится на пробел и проанализирует строку как два слова: {ls и , - l} , что вызовет команду not found ] (аргумент , - l} потерян):

 $ {ls ,-l}
 bash: {ls: command not found

Ваша строка: {ls; echo hi} не станет «расширением скобок» из-за два метасимвола , и пробел .

Он будет разбит на три части: {ls новая команда: echo hi} . Помните, что ; запускает начало новой команды. Команда {ls не будет найдена, и следующая команда напечатает hi} :

$ {ls;echo hi}
bash: {ls: command not found
hi}

Если ее поместить после какой-либо другой команды, она все равно запустит новую команду после ; :

$ echo {ls;echo hi}
{ls
hi}

Список

Одна из «составных команд» - это «Список скобок» (мои слова): {список; } .
Как видите, он определяется с помощью пробелов и закрывающих ; .
Пробелы и ; необходимы, потому что оба { и } являются «Зарезервированными словами ».

И поэтому, чтобы распознаваться как слова, они должны быть окружены метасимволами (почти всегда: пробел ).

Как описано в пункте 2 , связанная страница

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

Ваш пример: {ls; echo hi} не является списком.

Требуется закрывающий ; и один пробел (минимум) после {.Последний } определяется закрывающими ; .

Это список {ls; echo hi; } . И этот {ls; echo hi;} также (реже используется, но действителен) (спасибо @choroba за помощь).

$ { ls;echo hi; }
A-list-of-files
hi

Но как аргумент (оболочка знает разницу) для команды, он вызывает ошибку:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

Но будьте осторожны в том, что, по вашему мнению, обрабатывает оболочка:

$ echo { ls;echo hi;
{ ls
hi

0
03.12.2018, 10:17
3 ответа

Если вы уверены, что файлы для обработки небольшие (, как в вашем примере ), вы можете прочитать весь файл за один раз и проверить:

 file=$(<log3.txt)
 [[ $file =~ Host ]] && [[ $file =~ denied ]] && echo "$file"

Для больших файлов и при условии, что Hostпредшествует denied, вы можете использовать более быстрый (для внешних файлов )sed:

 <log3.txt sed -n '/^Host/!d;p;:1;n;/\<denied\>/{p;q};b1'

Имейте в виду, что это решение будет строго печатать первую строку, начинающуюся с Host, и , следующую за (, не в той же строке ), первой строке, которая содержит deniedкак слово.

Если вам нужно извлечь несколько пар Host-denied, измените qна b,это повторно -запустит цикл:

 <log3.txt sed -n '/^Host/!d;p;:1;n;/\<denied\>/{p;b};b1'

Аналогичное решение с awk, которое будет печатать последнюю Hostстроку, которая находится непосредственно перед deniedстрокой (попарно):

 awk  '  p==1 && /\<denied\>/     {d=$0;p=0}
                /^Host*/         {h=$0;p=1}
         { if(p==0&&h!=""&&d!="") {print h,RS,d;p=2} }
      '  <log3.txt

И та же логика (, за исключением того, что она будет соответствовать deniedв любом месте строки (, а не как слово ))в оболочке:

 #!/bin/sh
 p=0
 while IFS= read -r line; do
    if [ "$p" = 1 ]; then
        case $line in
           *denied*)        deniedline=$line; p=0   ;;
        esac
    fi

 case $line in
    Host*)               hostline=$line; p=1   ;;
 esac

 if [ "$p" = 0 ] && [ "$hostline" ] && [ "$deniedline" ]; then
    printf '%s\n%s\n' "$hostline" "$deniedline"
    p=2
 fi

 done <log3.txt
0
28.01.2020, 02:31

В любой отдельной итерации вашего цикла whileзначение $LINEне может быть одновременноHostиdenied. Это невозможно, учитывая данные в файле. Вот почему у вас нет выхода.

Если вы хотите увидеть все строки в файле, которые соответствуют двум словамHostили denied, используйте вместо этого grep:

grep -wF -e 'Host' -e 'Access denied' <log3.txt

Используемые здесь параметры гарантируют, что мы выполняем сравнение строк, а не соответствие регулярному выражению(-F)и что мы сопоставляем полные слова, а не подстроки(-w). Две строки запроса задаются с -e, и мы получим любую строку, содержащую , любую из них.

Если вы хотите сделать несколько более сложный запрос, который показывает только строки, содержащие два слова, если они оба присутствуют в файле, вы можете сделать это с помощью awkпрограммы:

awk '/Host/ { hostline=$0 } /Access denied/ { deniedline=$0 }
     END { if ((hostline != "") && (deniedline != ""))
               print hostline; print deniedline; }' <log3.txt

Здесь, если мы находим строку, соответствующую строке Host, мы сохраняем ее, и аналогично для строки Access denied. В конце, если обе строки что-то содержат, мы их печатаем.

В более или менее эквивалентном шелл-коде:

#!/bin/sh

while IFS= read -r line; do
    case $line in
        *Host*)
           hostline=$line   ;;
        *"Access denied"*)
           deniedline=$line ;;
    esac
done <log3.txt

if [ -n "$hostline" ] && [ -n "$deniedline" ]; then
    printf '%s\n%s\n' "$hostline" "$deniedline"
fi

Здесь я использую оператор case... esacдля сопоставления прочитанных данных. Используемые шаблоны представляют собой шаблоны подстановки имен файлов, а не регулярные выражения.

Похожие:

2
28.01.2020, 02:31

См. ответ @Kusalananda, почему ваше решение не работает.

Мое решение с использованиемgrep -z:

grep -zEo -e 'Host: (\w|\.)+\s+Access denied\s' log.txt

Во время -мес:

  • -E:использовать расширенное регулярное выражение
  • -o:печатать только совпадения
  • -z:используйте \0в качестве разделителя строк. Поскольку их нет, поиск выполняется по всему файлу, где \n— просто символ.
  • - e'Host: (\w|\.)+\s+Access denied\s':ищите :
    • "Host:"
    • Последовательность букв, цифр или точек
    • Пробел -символ класса (, который будет\n)
    • "Access denied"
    • Пробел -символ класса (, который будет\n). Это необходимо для перевода строки на выходе

Работает:

Host: denied1.com
Access denied
Host: ok.com
Access OK
Host: random.com

Host: denied2.com
Access denied

More stuff

Урожайность:

Host: denied1.com
Access denied
Host: denied2.com
Access denied
0
28.01.2020, 02:31

Теги

Похожие вопросы