Упрощенная причина - наличие одного символа: пробел .
Раскрытие фигурных скобок не обрабатывает (не заключенные в кавычки) пробелы.
Список {...}
требует (не заключенных в кавычки) пробелов.
Более подробный ответ: как оболочка анализирует командную строку .
Первым шагом к синтаксическому анализу (пониманию) командной строки является ее разделение на части.
Эти части (обычно называемые словами или токенами) являются результатом разделения командной строки на каждый метасимвол из ссылки :
- Разбивает команду на токены, разделенные фиксированный набор метасимволов: 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 , связанная страница
- Проверяет первый токен каждой команды, чтобы узнать, является ли он ...., {или (, тогда команда на самом деле является составной командой.
Ваш пример: {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
Если вы уверены, что файлы для обработки небольшие (, как в вашем примере ), вы можете прочитать весь файл за один раз и проверить:
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
В любой отдельной итерации вашего цикла 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
для сопоставления прочитанных данных. Используемые шаблоны представляют собой шаблоны подстановки имен файлов, а не регулярные выражения.
Похожие:
См. ответ @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