Подтвердить что ты не робот

В AWK можно указать "диапазоны" полей?

В AWK можно указать "диапазоны" полей?

Пример. Учитывая файл с разделителями табуляции "foo" со 100 полями в строке, я хочу напечатать только поля с 32 по 57 для каждой строки и сохранить результат в файле "bar". Что я делаю сейчас:

awk 'BEGIN{OFS="\t"}{print $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $50, $51, $52, $53, $54, $55, $56, $57}' foo > bar

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

Есть ли какая-то синтаксическая форма, которая позволяет мне говорить то же самое в более сжатой и менее подверженной ошибкам моде (например, "$ 32.. $57" )?

4b9b3361

Ответ 1

Вы можете сделать это в awk, используя интервалы RE. Например, для печати полей 3-6 записей в этом файле:

$ cat file
1 2 3 4 5 6 7 8 9
a b c d e f g h i

:

$ gawk 'BEGIN{f="([^ ]+ )"} {print gensub("("f"{2})("f"{4}).*","\\3","")}' file
3 4 5 6
c d e f

Я создаю сегмент RE f для представления каждого поля плюс его последующий разделитель полей (для удобства), тогда я использую это в gensub для удаления 2 из этих (то есть первых 2 полей), помните следующий 4 для справки позже, используя \3, а затем удалите то, что приходит после них. Для вашего файла с разделителями табуляции, где вы хотите распечатать поля 32-57 (т.е. 26 полей после первого 31), вы должны использовать:

gawk 'BEGIN{f="([^\t]+\t)"} {print gensub("("f"{31})("f"{26}).*","\\3","")}' file

В приведенном выше примере используется функция GNU awk для функции gensub(). С другими awks вы будете использовать sub() или match() и substr().

EDIT: здесь, как написать функцию для выполнения задания:

gawk '
function subflds(s,e,   f) {
   f="([^" FS "]+" FS ")"
   return gensub( "(" f "{" s-1 "})(" f "{" e-s+1 "}).*","\\3","")
}
{ print subflds(3,6) }
' file
3 4 5 6
c d e f

Просто установите FS по мере необходимости. Обратите внимание, что это потребует настройки для FS по умолчанию, если ваш входной файл может начинаться с пробелов и/или иметь несколько пробелов между полями и будет работать только в том случае, если ваш FS является единственным символом.

Ответ 2

Помимо awk ответа от @Jerry, есть и другие альтернативы:

Использование cut (предполагает разделитель табуляции по умолчанию):

cut -f32-58 foo >bar

Использование perl:

perl -nle '@a=split;print join "\t", @a[31..57]' foo >bar

Ответ 3

Мягко пересмотренная версия:

BEGIN { s = 32; e = 57; }

      { for (i=s; i<=e; i++) printf("%s%s", $(i), i<e ? OFS : "\n"); }

Ответ 4

Я опаздываю, но это быстро подходит к делу, поэтому я оставлю его здесь. В таких случаях я обычно просто удаляю поля, которые мне не нужны с gsub и печатью. Быстрый и грязный пример, так как вы знаете, что ваш файл разделен на вкладки, вы можете удалить первые 31 поля:

awk '{gsub(/^(\w\t){31}/,"");print}'

пример удаления 4 полей из-за лени:

printf "a\tb\tc\td\te\tf\n" | awk '{gsub(/^(\w\t){4}/,"");print}'

Вывод:

e   f

Это меньше, чем писать, легче запоминать и использует меньше циклов процессора, чем ужасные циклы.

Ответ 5

Вы можете использовать комбинацию петель и printf для этого в awk:

#!/bin/bash

start_field=32
end_field=58

awk -v start=$start_field -v end=$end_field 'BEGIN{OFS="\t"}
{for (i=start; i<=end; i++) {
    printf "%s" $i;
    if (i < end) {
        printf "%s", OFS;
    } else {
        printf "\n";
    }
}}'

Это выглядит немного взломанным, однако:

  • он правильно ограничивает ваш вывод на основе указанных OFS и
  • он обязательно напечатает новую строку в конце для каждой строки ввода в файле.

Ответ 6

Я не знаю, как сделать выбор диапазона полей в awk. Я знаю, как удалить поля в конце ввода (см. Ниже), но не легко в начале. Bellow, трудный способ сбросить поля в начале.

Если вы знаете символ c, который не включен в ваш ввод, вы можете использовать следующий awk script:

BEGIN { s = 32; e = 57; c = "#"; }
{ NF = e            # Drop the fields after e.
  $s = c $s         # Put a c in front of the s field.
  sub(".*"c, "")    # Drop the chars before c.
  print             # Print the edited line.
}

ИЗМЕНИТЬ

И я просто подумал, что вы всегда можете найти символ, который не находится во вводе: используйте \n.

Ответ 7

Я использую эту простую функцию, которая не проверяет, существует ли диапазон полей в строке.

function subby(f,l, s) {
  s = $f
  for(i=f+1;i<=l;i++)
    s = sprintf("%s %s",s,$i)

  return s
}

Ответ 8

(Я знаю, что OP запросил "в AWK", но...)

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

$ cat awk.txt

1 2 3 4 5 6 7 8 9

a b c d e f g h i

$ awk "{print $(c="" ;for i in {3..7}; do c=$c\$$i, ; done ; c=${c%%,} ; echo $c ;)}" awk.txt

3 4 5 6 7
c d e f g

объяснение;

c="" # var to hold args list
for i in {3..7} # the required variable range 3 - 7
do 
   # replace c value with concatenation of existing value, literal $, i value and a comma
   c=$c\$$i, 
done 
c=${c%%,} # remove trailing/final comma
echo $c #return the list string

помещается в одну строку, используя точки с запятой, внутри $() для оценки/расширения на месте.

Ответ 9

К сожалению, похоже, что у меня больше нет доступа к моей учетной записи, но в любом случае нет 50 представителей.

Ответ Боба может быть значительно упрощен с помощью 'seq':

echo $(seq -s ,\$ 5 9| cut -d, -f2-)
$6,$7,$8,$9

Небольшим недостатком является то, что вы должны указать свой первый номер поля как один ниже. Таким образом, чтобы получить поля с 3 по 7, я указываю 2 в качестве первого аргумента.

seq -s,\$ 2 7 устанавливает разделитель полей для seq at ', $' и дает 2, 3, $ 4, $ 5, $ 6, $ 7

cut -d, -f2- устанавливает разделитель поля на ',' и в основном вырезает все до первой запятой, показывая все начиная со второго поля. Таким образом, получается $ 3, $ 4, $ 5, $ 6, $ 7

В сочетании с ответом Боба мы получаем:

    $ cat awk.txt

    1 2 3 4 5 6 7 8 9

    a b c d e f g h i

    $ awk "{print $(seq -s ,\$ 2 7| cut -d, -f2-)}" awk.txt

    3 4 5 6 7

    c d e f g

    $