Извлеките несколько шаблонов из строки независимо от порядка

Aug 20 2020

Я новичок в написании сценариев для Unix, поэтому, пожалуйста, потерпите меня.

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

Пример файла -

process1 port=1234 appID=dummyAppId1 authenticate=true <some more params>
process3 port=1244 authenticate=false appID=dummyAppId2 <some more params>
process2 appID=dummyAppId3 port=1235 authenticate=true <some more params>

Желаемый результат -

1
port=1234 authenticate=true appID=dummyAppId1 
2
port=1244 authenticate=false appID=dummyAppId2
3
port=1235 authenticate=true appID=dummyAppId3

Цифры 1, 2 и 3 в каждой строке просто обозначают номер строки выходного файла.

Я уже пробовал использовать sed s/команду, но она зависит от порядка, а параметры во входном файле не соответствуют порядку - в результате некоторые строки во входном файле пропускаются.

Вот моя команда -

sed -nr 'appId/s/(\w+).*port=([^ ]+) .*authenticate=[^ ]+) .*appId=[^ ]+) .*/\2\t\3\t\4/p' | sed =

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

Благодаря!

Изменить 1: мне удалось использовать функцию утверждения с нулевой шириной grep таким образом -

grep -Po '(?<=pattern1=)[^ ,]+|(?<=pattern2=)[^ ,]+|(?<=pattern3=)[^ ,]+|(?<=pattern4=)[^ ,]+' filename

но это, похоже, дает результат для каждой строки в новых строках, т.е.

1234
true
dummyAppId1

Попытка выяснить, как получить его в одной строке с помощью grep (т.е. не путем объединения строк X в 1)

Редактировать 2: перепутал порядок параметров на входе

Изменить 3: извините, я должен был упомянуть об этом ранее - perlпохоже, это ограничено на машинах, над которыми я работаю. Хотя ответы, предоставленные Стефаном и Sundeep, отлично работают, когда я тестирую его локально, он не сработает на машинах, которые мне нужны, чтобы наконец запустить. Похоже, что в основном поддерживаются awk, grep и sed :(

Ответы

8 Sundeep Aug 20 2020 at 13:06

С awk(проверено GNU awk, не уверен, работает ли он с другими реализациями)

$ cat kv.awk /appID/ { for (i = 1; i <= NF; i++) { $i ~ /^port=/ && (a = $i) $i ~ /^authenticate=/ && (b = $i) $i ~ /^appID=/ && (c = $i) } print NR "\n" a, b, c } $ awk -v OFS='\t' -f kv.awk ip.txt
1
port=1234   authenticate=true   appID=dummyAppId1
2
port=1244   authenticate=false  appID=dummyAppId2
3
port=1235   authenticate=true   appID=dummyAppId3


С участием perl

$ # note that the order is changed for second line here $ cat ip.txt
process1 port=1234 authenticate=true appID=dummyAppId1 <some more params>
process3 port=1244 appID=dummyAppId2 authenticate=false <some more params>
process2 port=1235 authenticate=true appID=dummyAppId3 <some more params>

$ perl -lpe 's/(?=.*(port=[^ ]+))(?=.*(authenticate=[^ ]+))(?=.*(appID=[^ ]+)).*/$1\t$2\t$3/; print $.' ip.txt 
1
port=1234   authenticate=true   appID=dummyAppId1
2
port=1244   authenticate=false  appID=dummyAppId2
3
port=1235   authenticate=true   appID=dummyAppId3
  • (?=.*(port=[^ ]+)) первая группа захвата для port
  • (?=.*(authenticate=[^ ]+))вторая группа захвата authenticateи т. д.
  • print $. для номера строки
  • Чтобы избежать частичных совпадений, используйте \bportи \bappIDт. Д., Если достаточно границы слова. В противном случае используйте (?<!\S)(port=[^ ]+)для ограничения на основе пробелов.

Если вам нужно напечатать только строки, содержащие appIDили любое другое подобное условие, измените -lpeна -lneи измените print $.наprint "$.\n$_" if /appID/

7 StéphaneChazelas Aug 20 2020 at 13:15

С perl, вы можете использовать такой подход:

perl -lne 'my %h;
           $h{$1} = $& while /(\S+?)=(\S+)/g;
           print "@h{qw(port authenticate appID)}"'

Где вы создаете хеш-таблицу, ключами которой являются имена атрибутов, а значениями - name=values, а затем распечатываете те, которые вам нужны.

Замените $&на, $2если вам нужны только значения на выходе.

То же самое с awk:

awk '
  {
    split("", h)
    for (i = 1; i <= NF; i++)
      if (n = index($i, "=")) h[substr($i, 1, n - 1)] = $i
    print h["port"], h["authenticate"], h["appID"]
  }'

С помощью pcregrepвы можете:

pcregrep -o1 -o2 -o3 --om-separator=' ' '(?x)
  ^(?=.*?\s(port=\S+))
   (?=.*?\s(authenticate=\S+))
   (?=.*?\s(appID=\S+))'

(для этого требуются все три атрибута).

С sed:

sed 'G
     s/[[:space:]]\(port=[^[:space:]]*\).*\n.*/&\1/
     s/[[:space:]]\(authenticate=[^[:space:]]*\).*\n.*/& \1/
     s/[[:space:]]\(appID=[^[:space:]]*\).*\n.*/& \1/
     s/.*\n//'

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

1 LL3 Aug 20 2020 at 22:21

В соответствии с вашим EDIT 3, я думаю, вы все равно могли бы сделать это, sedесли бы вы сделали s///выражение для каждого параметра следующим образом:

sed -nE 's/^(.*)(appID=[^[:blank:]]+\s)(.*)$/\2\t\1\3/ s/^(.*)(authenticate=[^[:blank:]]+\s)(.*)$/\2\t\1\3/
         s/^(.*)(port=[^[:blank:]]+\s)(.*)$/\2\t\1\3/
         T;=
         s/^(([^[:blank:]]+\s+){,3}).*/\1/
         p'

Обратите внимание на обратный порядок sвыражений относительно желаемого порядка вывода. Нумерация также встроена в сценарий, выводя номера строк вывода, как вы упомянули, и выводит строку только в том случае, если какой-либо из требуемых параметров действительно присутствует в строке. Также обратите внимание, что я использую sed синтаксис GNU, поскольку вы использовали \dатомы, которые AFAIK не известны BSD sed. Возможен эквивалент, совместимый с POSIX, но он, вероятно, будет более расширенным.

Однако это уже ужасно долго и будет становиться все более сложным при увеличении параметров для вывода, поэтому awkсценарий, подобный приведенному ниже, может быть более универсальным:

awk '
    BEGIN {ac=ARGC; ARGC=0; OFS="\t"}
    {
        str=$0; NF=0
        for (i=1; i<ac; i++)
            if (match(str, ARGV[i]"=[^[:blank:]]*"))
                $(NF+1)=substr(str, RSTART, RLENGTH)
    }
    NF {print ++nr; print}
    ' -- port authenticate appID

Вы должны указать точные параметры, которые вы хотите вывести, и их порядок появления в качестве аргументов самого awk скрипта после --. Этот сценарий также печатает строку только тогда, когда в строке действительно присутствует хотя бы один из требуемых параметров.

1 EdMorton Aug 21 2020 at 20:27

Каждый раз, когда во входных данных есть пары name = value, я считаю, что лучше сначала создать массив, содержащий это отображение ( f[]) ниже, а затем вы можете получить доступ к значениям по их именам в любом порядке, который вам нравится, например:

$ awk -F'[ =]' '{ for (i=2;i<NF;i+=2) f[$i]=$i"="$(i+1)
    print f["port"], f["authenticate"], f["appID"]
}' file
port=1234 authenticate=true appID=dummyAppId1
port=1244 authenticate=false appID=dummyAppId2
port=1235 authenticate=true appID=dummyAppId3
Sumak Aug 20 2020 at 14:39

Если это может помочь другим пользователям с аналогичной проблемой, (подробное) предложение с использованием Ruby:

# passing the log file as parameter
lines = File.open(ARGV[0]).read.split("\n")

lines.each_with_index do |line, i|
  words  = line.split(' ')
  output = []

  puts i + 1
  output << words.select { |w| w =~ /port=\d+/ }
  output << words.select { |w| w =~ /authenticate=\w+/ }
  output << words.select { |w| w =~ /appID=\w+/ }

  puts output.join(' ')
end