Код/модуль для оценки логического выражения, которое будет использоваться в awk или bash в Linux

Я работаю над некоторыми скриптами bash с awk для поиска во многих файлах и возврата различных комбинаций строк, которые соответствуют различным комбинациям условий.

Я хотел бы каким-то образом добавить возможность для моих скриптов обрабатывать простые, но общие логические выражения "запрос" - такие вещи, как:

A AND (B OR C) AND NOT D

где A, B, C и D будут переменными bash или awk со значениями, такими как TRUE=0 и FALSE!=0, или наоборот.

Мой код устанавливает каждую из переменных, а затем передает логическое выражение с использованием этих переменных чему-то, что может его оценить.

На самом деле мне не нужно ничего особенного, например, вложенные скобки, но один уровень действительно поможет. Мне не нужна обширная проверка/восстановление ошибок выражений. Если я пишу плохое выражение, это моя проблема, если оно не уничтожает остальную часть моего скрипта чем-то вроде синтаксической ошибки bash/awk.

Решение может быть родным для bash или awk или внешней программой, вызываемой из любого языка, которая возвращает true или false (0/1).

Я знаю, как жестко закодировать такие вещи с помощью операторов if и т. д., но мне нужно иметь возможность изменять условия для каждого поиска, поэтому это нельзя жестко закодировать.

Я определенно недостаточно знаю, чтобы собрать свой собственный парсер с чем-то вроде lex/yacc.

Такие инструменты, как sed, find и grep, великолепны, но (по крайней мере, сами по себе) они не будут делать то, что мне нужно, и слишком разборчивы в синтаксисе, чтобы использовать их для написания специальных запросов.

В качестве одной из альтернатив я думаю о том, чтобы попытаться преобразовать мои запросы в простую математику и провести их через (( query )) в bash - например. для приведенного выше примера (без использования полностью защищенного синтаксиса, чтобы сделать пример уродливым, и со всеми переменными, установленными в 1 для истинного и 0 для ложного).

(( $A * ($B + $C) * (! $D) ))

person Joe    schedule 09.01.2015    source источник
comment
@Skynet - Спасибо за почти мгновенный ответ. Я не видел этого, когда просматривал подобные вопросы, прежде чем публиковать этот вопрос, но... Он не говорит мне, как перейти от несколько более удобной для пользователя формы к одной из форм, которые bash может успешно обрабатывать. Это будет часто используемая утилита, которую я хотел бы использовать немного проще, чем вводить синтаксически правильное выражение bash каждый раз, когда я ее использую.   -  person Joe    schedule 09.01.2015
comment
Хм. Я думаю, что это не дубликат. По крайней мере, не дублировать упомянутый Q...   -  person TrueY    schedule 09.01.2015


Ответы (1)


Я не совсем понял, какова ваша цель, но я создал небольшой bash для получения любого логического выражения и записи его таблицы истинности. Может быть, это поможет. Он обрабатывает любые фигурные скобки, выражения AND, OR и NOT. Переменная — это все, что начинается с буквы (на выбранном вами языке). Единственное, что нужно изменить, это переменную COND. Его также можно установить из командной строки. Это работает, только если ваш bash поддерживает связанные массивы (declare -A ).

Вот сценарий:

#!/usr/bin/bash

COND="A AND (B OR C) AND NOT DD"

# Convert to bash syntax
cond=${COND// AND / && }
cond=${cond// OR / || }
cond=${cond// NOT / ! }
cond=${cond//^NOT /! }
cond=${cond//(/ ( }
cond=${cond//)/ ) }
# Now $cond can be used in eval adding "((..))" around it.
# The rest of the script prints the complete truth-table

# Collect unique variables
declare -A hvars
for var in $cond; do
  [[ $var =~ ^[[:alpha:]] ]] && hvars[$var]=1
done
# Make vars array
vars=(${!hvars[@]})
# Number of variables
n=${#vars[@]}
# Number or rows in truth-table
((N=1<<n))

echo "${vars[@]} | $COND"
for ((i=0; i<N;++i)); do
  for ((b=0; b<n; ++b)) do
    var=${vars[b]}
    ((val=i & 1<<(n-1-b) ? 1 : 0))
    printf "%*d " ${#var} $val
    eval "(($var=$val))"
  done
  eval "((r=$cond))"
  echo "| $r";
done

Выход:

A B C DD | A AND (B OR C) AND NOT DD
0 0 0  0 | 0
0 0 0  1 | 0
0 0 1  0 | 0
0 0 1  1 | 0
0 1 0  0 | 0
0 1 0  1 | 0
0 1 1  0 | 0
0 1 1  1 | 0
1 0 0  0 | 0
1 0 0  1 | 0
1 0 1  0 | 1
1 0 1  1 | 0
1 1 0  0 | 1
1 1 0  1 | 0
1 1 1  0 | 1
1 1 1  1 | 0

Он переводит условие в удобочитаемый формат в формат bash. (от AND до &&, от OR до ||, от NOT до ! и вокруг ( и ) установите пробел. Необходимо не злоупотреблять )) в eval.). Затем он собирает все уникальные переменные. Затем вычисляет размер таблицы истинности. Затем делает петлю для каждого ряда. Внутри этого цикла создается вложенный цикл для вычисления значения каждой переменной и установки этого значения. В конце основного цикла вычисляется значение выражения.

Это не пуленепробиваемый. Не используйте специальные переменные в именах переменных (DD=XX или A'B).

Ваш код bash устанавливает используемые переменные, а затем вызывает преобразованное условие (см. cond) в eval (см. последний eval), он правильно оценивает условие. Тогда переменную $r можно использовать в своих целях...

Надеюсь, это поможет!

person TrueY    schedule 09.01.2015
comment
Это выглядит действительно круто, но потребуется немного времени, чтобы переварить это. Я вернусь. - person Joe; 11.01.2015
comment
@Joe: Вам нужны только строки COND= и cond= и строка eval "((r=$cond))". Остальные предназначены только для распечатки таблицы истинности. Результат устанавливается в $r. - person TrueY; 11.01.2015
comment
Это хороший кусок кода! Хотя я знаю, что он делает, я все еще не совсем понимаю, как работает ((val=i & 1<<(n-1-b) ? 1 : 0)). Я понимаю синтаксис, но не двоичную математику. Не могли бы вы немного разбить его для меня (без каламбура)? - person Joe; 26.01.2015
comment
@Joe: Если у вас есть n переменных, то у вас есть 2^n строк в таблице истинности. Номер строки находится в диапазоне от 0 до 2^n-1. Каждый бит номера строки можно рассматривать как значение переменной (например, A — 0-й бит, B — 1-й бит, C — 2-й бит). Каждое имя переменной и количество бит связаны через хэш vars, и его значением является b бит количества строк. На самом деле я перевернул счетчик битов (DD — это 0-й бит), чтобы получить более удобную таблицу истинности. Пример: строка 10 является 1010 двоичной, поэтому 0-й бит (может быть замаскирован как 2^0, 1‹‹0), 1-й бит маска 2^1 (1‹‹1), 2-й бит 2^2 (1‹‹2 ), так далее. - person TrueY; 26.01.2015
comment
@Джо. val ? true_val : false_val — это C-подобный ярлык. Если val истинно (не равно 0), то результатом будет true_val, что в данном случае равно 1. В противном случае 0. Но на самом деле вам это не нужно, так как это просто проверка того, хорошо ли работает решение... Это решение работает только с ограниченным числом переменных. Это предел 32 или 64, я не проверял... - person TrueY; 26.01.2015
comment
Спасибо за объяснение. Как я уже сказал, хороший код! - person Joe; 26.01.2015