В GNU Grep или другой стандартной команде bash возможно ли получить набор результатов из регулярного выражения? - PullRequest
1 голос
/ 23 февраля 2011

Рассмотрим следующее:

var="text more text and yet more text"
echo $var | egrep "yet more (text)"

Должно быть возможно получить результат регулярного выражения в виде строки: text

Однако я не вижу способа сделать это в bash с помощью grep или его братьев и сестер.

В perl, php или аналогичных регулярных выражениях:

$output = preg_match('/yet more (text)/', 'text more text yet more text');
$output[1] == "text";

Редактировать: Чтобы объяснить, почему я не могу просто использовать многократное регулярное выражение, в конце у меня будет регулярное выражение с несколькими из них (как показано ниже), поэтому я должен иметь возможность получить все из них. Это также исключает возможность использования lookahead / lookbehind (поскольку все они имеют переменную длину)

egrep -i "([0-9]+) +$USER +([0-9]+).+?(/tmp/Flash[0-9a-z]+) "

Пример ввода по запросу, прямо из lsof (замените $ USER на «j» для этих входных данных):

npviewer. 17875          j   11u      REG                8,8 59737848     524264 /tmp/FlashXXu8pvMg (deleted)
npviewer. 17875          j   17u      REG                8,8 16037387     524273 /tmp/FlashXXIBH29F (deleted)

Конечная цель - cp /proc/$var1/fd/$var2 ~/$var3 для каждой строки, что приводит к «загрузке» флеш-файлов (флеш-память использовалась для хранения в / tmp, но они ее помещали)

Пока у меня есть:

#!/bin/bash
regex="([0-9]+) +j +([0-9]+).+?/tmp/(Flash[0-9a-zA-Z]+)"

echo "npviewer. 17875          j   11u      REG                8,8 59737848     524264 /tmp/FlashXXYOvS8S (deleted)" |
sed -r -n -e " s%^.*?$regex.*?\$%\1 \2 \3%p " |
while read -a array
do
   echo /proc/${array[0]}/fd/${array[1]} ~/${array[2]}
done

Он обрезает первые цифры первого возвращаемого значения, и я недостаточно знаком с sed, чтобы понять, что не так.

Конечный результат для загрузки флэш-видео 10,2+ (включая, возможно, зашифрованные):

#!/bin/bash
lsof | grep "/tmp/Flash" | sed -r -n -e " s%^.+? ([0-9]+) +$USER +([0-9]+).+?/tmp/(Flash[0-9a-zA-Z]+).*?\$%\1 \2 \3%p " |
while read -a array
do
   cp /proc/${array[0]}/fd/${array[1]} ~/${array[2]}
done

Ответы [ 5 ]

4 голосов
/ 24 февраля 2011

Это невозможно при использовании grep или другого инструмента, вызываемого из приглашения / скрипта оболочки, потому что дочерний процесс не может изменить среду своего родительского процесса. Если вы используете bash 3.0 или выше, вы можете использовать внутрипроцессные регулярные выражения. Синтаксис perl-ish (= ~) и группы совпадений доступны через $ BASH_REMATCH [x], где x - группа совпадений.

4 голосов
/ 24 февраля 2011

Изменить: посмотрите на мой другой ответ для более простого решения только для bash.


Итак, вот решение , использующее sed для выбора правильных групп и их разделения . Позже вам все равно придется использовать bash для их чтения. (И таким образом это работает только в том случае, если в самих группах нет пробелов - в противном случае нам пришлось бы использовать другой символ-разделитель и исправить read, установив $IFS в это значение.)

#!/bin/bash
USER=j
regex=" ([0-9]+) +$USER +([0-9]+).+(/tmp/Flash[0-9a-zA-Z]+) "


sed -r -n -e " s%^.*$regex.*\$%\1 \2 \3%p " |
while read -a array
do
   cp /proc/${array[0]}/fd/${array[1]} ~/${array[2]}
done

Обратите внимание, что мне пришлось адаптировать вашу последнюю группу регулярных выражений, чтобы разрешать вводить заглавные буквы, и добавить пробел в начале, чтобы быть уверенным, что он захватит весь блок чисел. В качестве альтернативы здесь \b (ограничение по слову) тоже бы сработало.

Ах, я забыл упомянуть, что вы должны передать текст в этот скрипт, как это:

 ./grep-result.sh  < grep-result-test.txt 

(при условии, что ваши файлы названы так). Вместо этого вы можете добавить < grep-result-test после вызова sed (до |) или добавить строку с cat grep-result-test.txt |.

.

Как это работает?

  • sed -r -n вызывает sed в режиме расширенного регулярного выражения и автоматически ничего не печатает.
  • -e " s%^.*$regex.*\$%\1 \2 \3%p " предоставляет программу sed, которая состоит из одной команды s.

    • Я использую % вместо обычного / в качестве разделителя параметров, поскольку в регулярном выражении появляется /, и я не хочу его избегать.
    • Регулярное выражение для поиска имеет префикс ^.* и суффикс .*$, чтобы захватить всю строку (и избежать печати частей остальной части строки).

      Обратите внимание, что это .* захватывает жадность, поэтому мы должны вставить пробел в наше регулярное выражение, чтобы он не захватывал начало первой группы цифр.

    • Текст замены содержит три группы в скобках, разделенные пробелами.
    • флаг p в конце команды говорит о необходимости распечатывать пространство шаблона после замены. Поскольку мы захватили всю строку, пространство шаблона состоит только из текста замены.
  • Итак, вывод sed для вашего примера ввода будет таким:

    5 11 /tmp/FlashXXu8pvMg
    5 17 /tmp/FlashXXIBH29F
    

    Это намного удобнее для повторного использования, очевидно.

  • Теперь мы передадим этот вывод как вход в цикл while.

    • read -a array читает строку из стандартного ввода (который является выходом из sed, благодаря нашему каналу), разбивает ее на слова (в пробелах, табуляциях и новых строках) и помещает слова в переменную массива .

      Мы могли бы также написать read var1 var2 var3 вместо этого (предпочтительно с использованием лучших имен переменных), тогда первые два слова будут помещены в $var1 и $var2, с $var3, получая остальные.

    • Если read удалось прочитать строку (то есть не конец файла), выполняется тело цикла:
      • ${array[0]} расширяется до первого элемента массива и аналогично.
    • Когда заканчивается вход, цикл тоже заканчивается.
2 голосов
/ 24 февраля 2011

После создания моего sed -решения я также хотел попробовать подход pure-bash , предложенный Марком. Для меня все работает отлично.

#!/bin/bash

USER=j
regex=" ([0-9]+) +$USER +([0-9]+).+(/tmp/Flash[0-9a-zA-Z]+) "

while read 
do
    if [[ $REPLY =~ $regex ]]
    then
        echo cp /proc/${BASH_REMATCH[1]}/fd/${BASH_REMATCH[2]} ~/${BASH_REMATCH[3]}
    fi
done

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

То же, что и раньше: передать текст, который нужно отфильтровать, в этот скрипт.


Как это работает?

  • Как сказал Марк, специальная условная конструкция [[ ... ]] поддерживает бинарный оператор =~, который интерпретирует его правый операнд (после расширения параметра) как расширенное регулярное выражение (так, как мы хотим) и соответствует левому операнду против этого. (Мы снова добавили пробел впереди, чтобы избежать совпадения только с последней цифрой.)
  • Когда регулярное выражение совпадает, [[ ... ]] возвращает 0 (= true), а также помещает части, соответствующие отдельным группам (и всему выражению), в переменную массива BASH_REMATCH.
  • Таким образом, когда регулярное выражение совпадает, мы входим в блок then и выполняем там команды.
  • Здесь снова ${BASH_REMATCH[1]} - доступ к массиву к элементу массива, который соответствует первой сопоставленной группе. ([0] будет целой строкой.)

Еще одно примечание: оба моих скрипта принимают многострочный ввод и работают с каждой соответствующей строкой. Несоответствующие строки просто игнорируются. Если вы вводите только одну строку, вам не нужен цикл, достаточно простого if read ; then ... или даже read && [[ $REPLY =~ $regex ]] && ....

0 голосов
/ 23 февраля 2011

Ну, для вашего простого примера, вы можете сделать это:

var="text more text and yet more text"
echo $var | grep -e "yet more text" | grep -o "text"
0 голосов
/ 23 февраля 2011
echo "$var" | pcregrep -o "(?<=yet more )text"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...