[awk] Print all but the first three columns

Too cumbersome:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things

This question is related to awk

The answer is


Use cut:

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

e.g.: If you have file1 containing : car.is.nice.equal.bmw

Run : cut -d . -f1,3 file1 will print car.is.nice


As I was annoyed by the first highly upvoted but wrong answer I found enough to write a reply there, and here the wrong answers are marked as such, here is my bit. I do not like proposed solutions as I can see no reason to make answer so complex.

I have a log where after $5 with an IP address can be more text or no text. I need everything from the IP address to the end of the line should there be anything after $5. In my case, this is actualy withn an awk program, not an awk oneliner so awk must solve the problem. When I try to remove the first 4 fields using the old nice looking and most upvoted but completely wrong answer:

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}'

it spits out wrong and useless response (I added [] to demonstrate):

[    37.244.182.218 one two three]

Instead, if columns are fixed width until the cut point and awk is needed, the correct and quite simple answer is:

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

which produces the desired output:

[37.244.182.218 one two three]

awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Input

1 2 3 4 5 6 7

Output

4 5 6 7

Options 1 to 3 have issues with multiple whitespace (but are simple). That is the reason to develop options 4 and 5, which process multiple white spaces with no problem. Of course, if options 4 or 5 are used with n=0 both will preserve any leading whitespace as n=0 means no splitting.

Option 1

A simple cut solution (works with single delimiters):

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

Option 2

Forcing an awk re-calc sometimes solve the problem (works with some versions of awk) of added leading spaces:

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

Option 3

Printing each field formated with printf will give more control:

$ 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

However, all previous answers change all FS between fields to OFS. Let's build a couple of solutions to that.

Option 4

A loop with sub to remove fields and delimiters is more portable, and doesn't trigger a change of FS to 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

NOTE: The "^["FS"]*" is to accept an input with leading spaces.

Option 5

It is quite possible to build a solution that does not add extra leading or trailing whitespace, and preserve existing whitespace using the function gensub from GNU awk, as this:

$ 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 

It also may be used to swap a field list given a count 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     !

Of course, in such case, the OFS is used to separate both parts of the line, and the trailing white space of the fields is still printed.

Note1: ["FS"]* is used to allow leading spaces in the input line.


Perl solution which does not add leading or trailing whitespace:

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

The perl @F autosplit array starts at index 0 while awk fields start with $1


Perl solution for comma-delimited data:

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

Python solution:

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


The correct way to do this is with an RE interval because it lets you simply state how many fields to skip, and retains inter-field spacing for the remaining fields.

e.g. to skip the first 3 fields without affecting spacing between remaining fields given the format of input we seem to be discussing in this question is simply:

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

If you want to accommodate leading spaces and non-blank spaces, but again with the default FS, then it's:

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

If you have an FS that's an RE you can't negate in a character set, you can convert it to a single char first (RS is ideal if it's a single char since an RS CANNOT appear within a field, otherwise consider SUBSEP), then apply the RE interval subsitution, then convert to the OFS. e.g. if chains of "."s separated the fields:

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

Obviously if OFS is a single char AND it can't appear in the input fields you can reduce that to:

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

Then you have the same issue as with all the loop-based solutions that reassign the fields - the FSs are converted to OFSs. If that's an issue, you need to look into GNU awks' patsplit() function.


I can't believe nobody offered plain shell:

while read -r a b c d; do echo "$d"; done < file

Try this:

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

use cut

$ cut -f4-13 file

or if you insist on awk and $13 is the last field

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

else

$ awk '{for(i=4;i<=13;i++)printf "%s ",$i;printf "\n"}' file

This isn't very far from some of the previous answers, but does solve a couple of issues:

cols.sh:

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

Which you can now call with an argument that will be the starting column:

$ 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

Or:

$ 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

This is 1-indexed; if you prefer zero indexed, use i=s + 1 instead.

Moreover, if you would like to have to arguments for the starting index and end index, change the file to:

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

For example:

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

The %-5s aligns the result as 5-character-wide columns; if this isn't enough, increase the number, or use %s (with a space) instead if you don't care about alignment.


Pretty much all the answers currently add either leading spaces, trailing spaces or some other separator issue. To select from the fourth field where the separator is whitespace and the output separator is a single space using awk would be:

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

To parametrize the starting field you could do:

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

And also the ending field:

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

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

For me the most compact and compliant solution to the request is

$ 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}'

And if you have more lines to process as for instance file foo.txt, don't forget to reset i to 0:

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

Thanks your forum.


Cut has a --complement flag that makes it easy (and fast) to delete columns. The resulting syntax is analogous with what you want to do -- making the solution easier to read/understand. Complement also works for the case where you would like to delete non-contiguous columns.

$ foo='1 2 3 %s 5 6 7'
$ echo "$foo" | cut --complement -d' ' -f1-3
%s 5 6 7
$

Another way to avoid using the print statement:

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

In awk when a condition is true print is the default action.


AWK printf-based solution that avoids % problem, and is unique in that it returns nothing (no return character) if there are less than 4 columns to print:

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

Testing:

$ 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) }'
$

awk '{for(i=1;i<4;i++) $i="";print}' file

I've found this other possibility, maybe it could be useful also...

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

Note: 1. For tabular data and from column $1 to $14