В 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" )?
Ответы
Ответ 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
$