Вывести все столбцы, кроме первых трех

112

Слишком громоздко:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things
HHH
источник
43
Есть ли причина, по которой вы не можете просто использовать cut -f3-?
Cascabel
1
@hhh хороший .. Мне нравится идея краткого ответа.
Крис Сеймур,
2
@Jefromi - из-за проблем с буферизацией строк при вырезании, чего нет в awk: stackoverflow.com/questions/14360640/…
sdaau
@Jefromi - тоже cutне имеет регулярных выражений перед {}действиями, да и то тупее с разделителями полей (переменное количество пробелов?), И их приходится указывать вручную. Я думаю, ОП хотел услышать о какой-то shift Nкоманде, которой не существует. Самый близкий $1="";$2="";(...);print}, но в моем случае он оставляет некоторые ведущие пробелы (возможно, разделители).
Tomasz Gandor

Ответы:

50

Решение, которое не добавляет лишних начальных или конечных пробелов :

awk '{ for(i=4; i<NF; i++) printf "%s",$i OFS; if(NF) printf "%s",$NF; printf ORS}'

### Example ###
$ echo '1 2 3 4 5 6 7' |
  awk '{for(i=4;i<NF;i++)printf"%s",$i OFS;if(NF)printf"%s",$NF;printf ORS}' |
  tr ' ' '-'
4-5-6-7

Sudo_O предлагает элегантное улучшение с использованием тернарного оператораNF?ORS:OFS

$ echo '1 2 3 4 5 6 7' |
  awk '{ for(i=4; i<=NF; i++) printf "%s",$i (i==NF?ORS:OFS) }' |
  tr ' ' '-'
4-5-6-7

EdMorton предлагает решение, сохраняющее исходные пробелы между полями:

$ echo '1   2 3 4   5    6 7' |
  awk '{ sub(/([^ ]+ +){3}/,"") }1' |
  tr ' ' '-'
4---5----6-7

BinaryZebra также предоставляет два потрясающих решения:
(эти решения даже сохраняют конечные пробелы из исходной строки)

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ for ( i=1; i<=n; i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 ' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

$ echo -e ' 1   2\t \t3     4   5   6 7 \t 8\t ' |
  awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }' |
  sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'
"4...5...6.7.->.8->."

Решение, данное larsr в комментариях, почти правильное:

$ echo '1 2 3 4 5 6 7' | 
  awk '{for (i=3;i<=NF;i++) $(i-2)=$i; NF=NF-2; print $0}' | tr  ' ' '-'
3-4-5-6-7

Это фиксированная и параметризованная версия решения larsr :

$ echo '1 2 3 4 5 6 7' | 
  awk '{for(i=n;i<=NF;i++)$(i-(n-1))=$i;NF=NF-(n-1);print $0}' n=4 | tr ' ' '-'
4-5-6-7

Все остальные ответы до сентября 2013 года хороши, но добавляют лишние пробелы:

olibre
источник
Ответ ЭдМортона не сработал для меня (bash 4.1.2 (1) -release, GNU Awk 3.1.7 или bash 3.2.25 (1) -release, GNU Awk 3.1.5), но нашел здесь другой способ:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
1
@elysch нет, это не будет работать в целом, это просто работает с некоторыми конкретными входными значениями. См. Комментарий, который я добавил под вашим комментарием под моим ответом.
Эд Мортон
1
Привет @fedorqui. Мой ответ первый. В моем первоначальном ответе я объяснял, почему другой ответ был неправильным (лишние начальные или конечные пробелы). Некоторые люди предлагали улучшения в комментариях. Мы попросили ОП выбрать более правильный ответ, и он выбрал мой. После того, как некоторые другие участники отредактировали мой ответ, чтобы сослаться на ответ (см. Историю). Вам это понятно? Что вы посоветуете мне, чтобы мой ответ был понятнее? Cheers ;-)
olibre
1
Вы абсолютно правы, и я очень сожалею о своем недоразумении. Я быстро прочитал ответ и не заметил вашего исходного ответа (да, я прочитал слишком быстро). +1 для самого ответа, используя красивый трюк, чтобы перейти к NF-1 и затем распечатать последний элемент, чтобы избежать лишних пробелов. И еще раз извините! (удаляю свой комментарий через день или около того, чтобы предотвратить недопонимание со стороны будущих читателей).
fedorqui 'SO, перестань причинять вред'
1
Я бы использовал какие-то заголовки: <ваш ответ>, а затем горизонтальное правило, за которым следует большой заголовок «Сравнение других ответов». В противном случае переместите это сравнение к другому ответу, поскольку, очевидно, люди предпочитают короткие ответы в видении «дай мне мой код»
:)
75
awk '{for(i=1;i<4;i++) $i="";print}' file
jiju
источник
4
Это оставит интерлиньяж, OFSпоскольку вы не имеете дело с NFпробелом в записях.
Крис Сеймур
70

использовать разрез

$ cut -f4-13 file

или если вы настаиваете на awk и $ 13 - последнее поле

$ awk '{$1=$2=$3="";print}' file

еще

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file
ghostdog74
источник
14
вероятно, лучше использовать «NF», чем «13» в последнем примере.
Гленн Джекман
2
2 сценария, который должен решить OP. если 13 - последнее поле, можно использовать NF. Если нет, то уместно использовать 13.
ghostdog74
3
2-й должен удалить 3 копии OFS с начала 0 $. 3-й будет лучше с printf "%s ",$i, так как вы не знаете, $iможет ли он содержать %sили что-то подобное. Но это напечатало бы лишний пробел в конце.
dubiousjim
38

Попробуй это:

awk '{ $1=""; $2=""; $3=""; print $0 }'
LHF
источник
1
Это приятно тем, насколько динамично. Вы можете добавлять столбцы в конце и не переписывать свои скрипты.
MinceMan
1
Это демонстрирует точную проблему, которую пытается решить вопрос, просто сделайте наоборот. А как насчет того, чтобы напечатать с сотого поля? Обратите внимание на то, что вы не имеете дела, NFпоэтому вы уходите в лидеры OFS.
Крис Сеймур
24

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

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

$ echo '1   2 3 4   5    6' |
awk '{sub(/([^ ]+ +){3}/,"")}1'
4   5    6

Если вы хотите разместить ведущие пробелы и непустые пробелы, но снова с FS по умолчанию, тогда это:

$ echo '  1   2 3 4   5    6' |
awk '{sub(/[[:space:]]*([^[:space:]]+[[:space:]]+){3}/,"")}1'
4   5    6

Если у вас есть FS, который является RE, который вы не можете отрицать в наборе символов, вы можете сначала преобразовать его в один символ (RS идеально подходит, если это один символ, поскольку RS НЕ МОЖЕТ появляться в поле, в противном случае рассмотрите SUBSEP), затем примените замену интервала RE, затем преобразуйте в OFS. например, если цепочки "." разделяют поля:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,RS);sub("([^"RS"]+["RS"]+){3}","");gsub(RS,OFS)}1'
4 5 6

Очевидно, что если OFS - это один символ и он не может отображаться в полях ввода, вы можете уменьшить его до:

$ echo '1...2.3.4...5....6' |
awk -F'[.]+' '{gsub(FS,OFS); sub("([^"OFS"]+["OFS"]+){3}","")}1'
4 5 6

Тогда у вас будет та же проблема, что и со всеми решениями на основе цикла, которые переназначают поля - FS преобразуются в OFS. Если это проблема, вам нужно изучить функцию patsplit () GNU awks.

Эд Мортон
источник
У меня не сработало (bash 4.1.2 (1) -release, GNU Awk 3.1.7 или bash 3.2.25 (1) -release, GNU Awk 3.1.5), но нашел здесь другой способ:echo ' This is a test' | awk '{print substr($0, index($0,$3))}'
elysch
2
Нет, это не сработает, если $ 1 или $ 2 содержат строку, установленную для $ 3. Попробуйте, например, echo ' That is a test' | awk '{print substr($0, index($0,$3))}'и вы обнаружите, aчто сумма в 3 доллара совпадает с aвнутренней частью Thatв 1 доллар. В очень старой версии gawk, такой как у вас, вам нужно включить интервалы RE с помощью флага --re-interval.
Эд Мортон
2
Вы правы, не заметили. Кстати, очень признателен за ваш комментарий. Много раз хотел использовать регулярное выражение с "{}" для указания количества элементов и никогда не видел "--re-interval" в человеке. +1 для вас.
elysch
1
1является истинным условием и поэтому вызывает действие awk по умолчанию для печати текущей записи.
Эд Мортон
1
Не знаю, насколько это канонично, но я добавил ответ сейчас.
Эд Мортон
10

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

awk '{for(i=4;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' file

Чтобы параметризовать начальное поле, вы можете:

awk '{for(i=n;i<=NF;i++)printf "%s",$i (i==NF?ORS:OFS)}' n=4 file

А также конечное поле:

awk '{for(i=n;i<=m=(m>NF?NF:m);i++)printf "%s",$i (i==m?ORS:OFS)}' n=4 m=10 file
Крис Сеймур
источник
6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Ввод

1 2 3 4 5 6 7

Вывод

4 5 6 7

источник
4
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'
Вецин
источник
3
Или, чтобы разместить их в той же строке, назначьте 3 доллара на 1 и т. Д., А затем измените NF на нужное количество полей. echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'
larsr
Привет @larsr. Предлагаемая вами командная строка - единственный правильный ответ. Все остальные ответы добавляют дополнительные пробелы (ведущие или конечные). Пожалуйста, опубликуйте свою командную строку в новом ответе, я проголосую за нее ;-)
olibre
1
Привет, @sudo_O, я говорил с @larsr о командной строке, которую он предложил в своем комментарии. Я потратил около пяти минут, прежде чем выяснить quiproco (недоразумение). Я согласен, ответ @Vetsin вставляет новые строки ( ORS) между полями. Браво за вашу инициативу (мне нравится ваш ответ). Cheers
olibre
3

Другой способ избежать использования оператора печати:

 $ awk '{$1=$2=$3=""}sub("^"FS"*","")' file

В awk, когда условие истинно, печать является действием по умолчанию.

Хуан Диего Годой Роблес
источник
У этого есть все проблемы, которые есть у @lhf answer .. он просто короче.
Крис Сеймур
Очень хорошая идея;) Лучше моего ответа! (Я уже проголосовал за ваш ответ в прошлом году) Ура
olibre
Это должно быть: awk '{$1=$2=$3=""}sub("^"OFS"+","")' fileкак и OFS, что осталось после изменения содержимого $ 1, $ 2 и $ 3.
3

Не могу поверить, что никто не предлагал простую оболочку:

while read -r a b c d; do echo "$d"; done < file
Гленн Джекман
источник
+1 за аналогичное решение ... Но это может иметь проблемы с производительностью, если fileоно велико (> 10-30 КБ). Для больших файлов awkрешение работает лучше.
TrueY
3

Варианты с 1 по 3 имеют проблемы с несколькими пробелами (но они просты). Это причина для разработки вариантов 4 и 5, которые без проблем обрабатывают несколько пробелов. Конечно, если варианты 4 или 5 используются с n=0обоими, все начальные пробелы сохранятся, что n=0означает отсутствие разделения.

Опция 1

Простое решение для разрезания (работает с одиночными разделителями):

$ echo '1 2 3 4 5 6 7 8' | cut -d' ' -f4-
4 5 6 7 8

Вариант 2

Принудительное повторное вычисление awk иногда решает проблему (работает с некоторыми версиями awk) с добавленными ведущими пробелами:

$ echo '1 2 3 4 5 6 7 8' | awk '{ $1=$2=$3="";$0=$0;} NF=NF'
4 5 6 7 8

Вариант 3

Печать каждого поля, сформированного с помощью printf, даст больше контроля:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ for (i=n+1; i<=NF; i++){printf("%s%s",$i,i==NF?RS:OFS);} }'
4 5 6 7 8

Однако все предыдущие ответы меняют все FS между полями на OFS. Давайте создадим для этого пару решений.

Вариант 4

Цикл с подпрограммой для удаления полей и разделителей более переносим и не вызывает изменения FS на OFS:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ for(i=1;i<=n;i++) { sub("^["FS"]*[^"FS"]+["FS"]+","",$0);} } 1 '
4   5   6 7     8

ПРИМЕЧАНИЕ: «^ [« FS »] *» означает ввод с ведущими пробелами.

Вариант 5

Вполне возможно построить решение, которое не добавит лишних начальных или конечных пробелов и сохранит существующие пробелы, используя функцию gensubиз GNU awk, например:

$ echo '    1    2  3     4   5   6 7     8  ' |
awk -v n=3 '{ print gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1); }'
4   5   6 7     8 

Его также можно использовать для замены списка полей с учетом количества n:

$ echo '    1    2  3     4   5   6 7     8  ' |
  awk -v n=3 '{ a=gensub("["FS"]*([^"FS"]+["FS"]+){"n"}","",1);
                b=gensub("^(.*)("a")","\\1",1);
                print "|"a"|","!"b"!";
               }'
|4   5   6 7     8  | !    1    2  3     !

Конечно, в таком случае OFS используется для разделения обеих частей строки, а конечный пробел полей все еще печатается.

Примечание1: ["FS"]* используется для разрешения ведущих пробелов в строке ввода.


источник
Привет БЖ Ваш ответ приятный. Но вариант 3 не работает со строкой, начинающейся с пробела (например " 1 2 3 4 5 6 7 8 "). Вариант 4 хорош, но оставьте начальный пробел, используя строку, начинающуюся с пробела. Как вы думаете, поправимо ли это? Вы можете использовать команду echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'для проверки
начальных
Привет @olibre. То, что вариант 3 не работает с пробелом, является причиной разработки вариантов 4 и 5. Вариант 4 оставляет начальный пробел только в том случае, если он есть во входных данных и n установлено в 0 (n = 0). Я считаю, что это правильный ответ, когда нет выбора полей (ничего, чтобы исправить IMO). Приветствия.
Отлично. Спасибо за дополнительную информацию :-) Пожалуйста, улучшите свой ответ,
добавив
Отлично :-) Как жаль, что ваш пользователь отключен :-(
olibre
1

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

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$
Майкл Бэк
источник
Не могли бы вы объяснить свой ответ подробнее?
Зулу,
Помогает ли приведенное выше редактирование в понимании? Дело в том, чтобы использовать флаг дополнения cut. Решение должно быть более быстрой и лаконичной, чем решения на основе AWK или Perl. Также можно вырезать произвольные столбцы.
Майкл Бэк
1

Решение Perl, которое не добавляет начальные и конечные пробелы:

perl -lane 'splice @F,0,3; print join " ",@F' file

@FМассив perl autosplit начинается с индекса, 0а поля awk начинаются с$1


Решение Perl для данных, разделенных запятыми:

perl -F, -lane 'splice @F,0,3; print join ",",@F' file

Решение Python:

python -c "import sys;[sys.stdout.write(' '.join(line.split()[3:]) + '\n') for line in sys.stdin]" < file

Крис Кокнат
источник
0

Для меня наиболее компактным и совместимым решением запроса является

$ a='1   2\t \t3     4   5   6 7 \t 8\t '; 
$ echo -e "$a" | awk -v n=3 '{while (i<n) {i++; sub($1 FS"*", "")}; print $0}'

И если вам нужно обработать больше строк, например, файл foo.txt , не забудьте сбросить i до 0:

$ awk -v n=3 '{i=0; while (i<n) {i++; sub($1 FS"*", "")}; print $0}' foo.txt

Спасибо вашему форуму.

пользователь8008888
источник
0

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

У меня есть журнал, в котором после 5 долларов с IP-адресом может быть больше текста или нет текста. Мне нужно все, от IP-адреса до конца строки, если что-то стоит после 5 долларов. В моем случае это действительно с awk-программой, а не с одной строкой awk, поэтому awk должен решить проблему. Когда я пытаюсь удалить первые 4 поля, используя старый красивый и получивший наибольшее количество голосов, но совершенно неправильный ответ:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{$1=$2=$3=$4=""; printf "[%s]\n", $0}'

он выдает неправильный и бесполезный ответ (я добавил [] для демонстрации):

[    37.244.182.218 one two three]

Вместо этого, если столбцы имеют фиксированную ширину до тех пор, пока не потребуется точка отсечения и awk, правильный и довольно простой ответ:

echo "  7 27.10.16. Thu 11:57:18 37.244.182.218 one two three" | awk '{printf "[%s]\n", substr($0,28)}'

который дает желаемый результат:

[37.244.182.218 one two three]
Pila
источник
0

Я нашел эту возможность, может быть, она тоже может быть полезна ...

awk 'BEGIN {OFS=ORS="\t" }; {for(i=1; i<14; i++) print $i " "; print $NF "\n" }' your_file

Примечание. 1. Для табличных данных и столбцов от 1 до 14 долларов.

jgarces
источник
0

Используйте разрез:

cut -d <The character between characters> -f <number of first column>,<number of last column> <file name>

например: Если у вас есть file1:car.is.nice.equal.bmw

Выполнить: cut -d . -f1,3 file1 напечатаетcar.is.nice

Заид
источник
Похоже, ваше решение может быть обратным. Пожалуйста, проверьте заголовок вопроса Распечатайте все * кроме * первых трех столбцов
Стефан Крейн
-1

Это не очень далеко от некоторых из предыдущих ответов, но решает несколько проблем:

cols.sh:

#!/bin/bash
awk -v s=$1 '{for(i=s; i<=NF;i++) printf "%-5s", $i; print "" }'

Который теперь можно вызвать с аргументом, который будет начальным столбцом:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 3 
3    4    5    6    7    8    9    10   11   12   13   14

Или:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 
7    8    9    10   11   12   13   14

Это 1-индексированный; если вы предпочитаете нулевая индексация, используйте i=s + 1вместо этого.

Более того, если вы хотите иметь аргументы для начального индекса и конечного индекса, измените файл на:

#!/bin/bash
awk -v s=$1 -v e=$2 '{for(i=s; i<=e;i++) printf "%-5s", $i; print "" }'

Например:

$ echo "1 2 3 4 5 6 7 8 9 10 11 12 13 14" | ./cols.sh 7 9 
7    8    9

В %-5sсовмещается результат как 5-символьный широкими колонны; если этого недостаточно, увеличьте число или используйте %s(с пробелом) вместо этого, если вас не волнует выравнивание.

user2141650
источник
-1

Решение на основе AWK printf, которое позволяет избежать проблемы с% и уникально тем, что ничего не возвращает (без символа возврата), если для печати осталось менее 4 столбцов:

awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'

Тестирование:

$ x='1 2 3 %s 4 5 6'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
%s 4 5 6
$ x='1 2 3'
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$ x='1 2 3 '
$ echo "$x" | awk 'NF > 3 { for(i=4; i<NF; i++) printf("%s ", $(i)); print $(i) }'
$
Майкл Бэк
источник