Извлечение определенных значений имени столбца с помощью sed/awk/perl

У меня есть входной файл, например:

a=1 b=2 c=3 d=4
a=2 b=3
a=0 c=7
a=3 b=9 c=0 d=5
a=4 d=1
c=9

Предположим, что порядок имен столбцов (a,b,c и d) остается прежним. Как мне написать сценарий/команду, которая поможет мне извлечь значения, характерные для столбцов b и d? Итак, мой вывод должен быть:

b=2 d=4
b=3

b=9 d=5
d=1

Я мог бы написать «не очень хорошую» команду awk, используя несколько разделителей, чтобы отфильтровать их, используя каналы для использования опции -F, но я уверен, что есть и более элегантный способ сделать это.

Пожалуйста, помогите.


person Sumit    schedule 27.10.2009    source источник


Ответы (5)


Вот однострочная версия:

$ perl -lpe '@x=/([bd]=[0-9])/g; $_="@x"' test.txt

m//g в контексте списка возвращает все совпадения в виде списка.

#!/usr/bin/perl
use strict; use warnings;

while ( <DATA> ) {
    if( my @cols = /([bd]=[0-9])/g ) {
        print "@cols";
    }
    print "\n";
}

__DATA__
a=1 b=2 c=3 d=4
a=2 b=3
a=0 c=7
a=3 b=9 c=0 d=5
a=4 d=1
c=9

Выход:

C:\Temp> t.pl
b=2 d=4
b=3

b=9 d=5
d=1
person Sinan Ünür    schedule 27.10.2009
comment
Этот вывод не то, что он просил. - person rsp; 27.10.2009
comment
@rsp Да, я как-то пропустил образец вывода в первый раз. Теперь это исправлено. - person Sinan Ünür; 27.10.2009

Sed сделает это очень красиво:

sed -e 's/[^bd]=[^ ]*//g' -e 's/^ *//' -e 's/ *$//' < filename

Первое регулярное выражение очищает ненужные поля (все, кроме b и d), поэтому его можно изменить, если вы передумаете. Два других удаляют начальные и конечные пробелы.

person Beta    schedule 27.10.2009

В Руби:

#!/usr/bin/env ruby
filename = ARGV[0]
fields = ARGV[1..ARGV.length]

File.open(filename) do |file|
  file.each_line do |line|
    pairs = line.split(' ').map { |expression| expression.split('=') }
    value_hash = Hash[pairs]

    requested_fields = []

    fields.each do |field|
      requested_fields << "#{field}=#{value_hash[field]}" unless value_hash[field].nil?
    end

    puts requested_fields.join(' ')
  end
end

Звоните с помощью ruby ruby_script_name.rb input_file.txt field1 field2.

Мне нравится, насколько коротким является решение sed/perl, но насколько легко его изменить, чтобы использовать более длинные имена полей? Похоже, что регулярное выражение быстро станет беспорядочным... В любом случае, эта стратегия будет применима и здесь, если вы захотите ее использовать.

person Benjamin Oakes    schedule 27.10.2009
comment
Ruby может делать однострочники, даже если это не самое распространенное или предпочтительное использование языка: fepus.net /ruby1line.txt - person Telemachus; 27.10.2009
comment
Спасибо, Телемах. Я буду использовать подобные однострочники, но я обнаружил, что они имеют ограниченное применение в долгосрочной перспективе. То есть, я с удовольствием использую их для вещей, которых я знаю, что они будут использоваться лишь несколько раз и не нуждаются в сопровождении — чаще всего я использую их в vim (см. rubydo). (Все, что запрошено кем-то другим, имеет тенденцию полагаться, поэтому плохо, когда вы возвращаетесь к этому через 3 месяца и не можете понять, почему ломается цепочка из 10 регулярных выражений. Я был там со своим кодом и другими люди, и это не весело.) В зависимости от того, что нужно спрашивающему, любой из них может быть полезен. - person Benjamin Oakes; 27.10.2009
comment
(Поскольку этот пример ввода кажется простым на данный момент, однострочник может быть лучшим. Однако по мере продвижения все становится сложнее...) - person Benjamin Oakes; 27.10.2009

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

#! /usr/bin/env perl
use warnings;
use strict;

my @lines;

while(<>){
  my %kv = /([a-z])=([0-9])/ig;
  push @lines, \%kv;
}

for my $kv (@lines){
  # $kv->{a} ||= 1;
  # next unless $kv->{c};

  print "b=$kv->{b} " if defined $kv->{b};
  print "b=$kv->{d} " if defined $kv->{d};
  print "\n";
}
person Brad Gilbert    schedule 27.10.2009
comment
@Brad Я рад, что у кого-то еще была такая же идея (см. первую версию моего поста, за которую проголосовали, пока я ее расширял). +1. Обратите внимание, что вы должны использовать if defined $kv->{b}, потому что 0 является допустимым значением. - person Sinan Ünür; 27.10.2009

Ясно, что PostScript - это то, что нужно... XD

(%stdin) (r) file
{
    dup 100 string readline not {exit} if
    {
        dup () eq {pop exit} if
        token pop 3 string cvs
        dup 0 get << 98 / 100 / >> exch known
        {print ( ) print} {pop} ifelse
    } loop
    / =
} loop

Использование: gs -q -dNOPROMPT -dNODISPLAY -dBATCH thisfile.ps < input

Примечания: замените << 98 / 100 / >> соответствующими значениями ASCII (98 = b, 100 = d), за каждым из которых следует косая черта, разделенная пробелом (хотя вам не обязательно использовать косую черту, это просто фиктивный объект). Например, чтобы выбрать «c», «e» и «f», используйте << 99 / 101 / 102 / >>

Каждая строка может содержать не более 100 символов; если ваши строки длиннее, замените 100 string на большее число. Аналогичным образом замените 3 string, если ваши записи x=# длиннее трех символов. Однако это не работает, если x состоит из более чем одного символа.

person KirarinSnow    schedule 29.10.2009