125

Too cumbersome:

awk '{print " "$4" "$5" "$6" "$7" "$8" "$9" "$10" "$11" "$12" "$13}' things
icedwater
  • 4,701
  • 3
  • 35
  • 50
hhh
  • 50,788
  • 62
  • 179
  • 282
  • 43
    Is there any reason you can't just use `cut -f3-`? – Cascabel Apr 13 '10 at 00:38
  • 1
    @hhh nice one.. I like the idea of a summary answer. – Chris Seymour Sep 22 '13 at 21:54
  • 2
    @Jefromi - because there are line buffering issues with cut, which awk doesn't have: http://stackoverflow.com/questions/14360640/tail-f-into-grep-into-cut-not-working-properly – sdaau Nov 27 '13 at 16:57
  • 2
    possible duplicate of [Using awk to print all columns from the nth to the last](http://stackoverflow.com/questions/2961635/using-awk-to-print-all-columns-from-the-nth-to-the-last) – Wladimir Palant Jan 06 '14 at 07:49
  • @Jefromi - also `cut` does not have regexes before `{}` actions, and then it's way dumber with field delimiters (variable number of spaces?), and you have to specify them manually. I think the OP wanted to hear about some `shift N` command, which does not exist. The closest is `$1="";$2="";(...);print}`, but in my case it leaves some leading spaces (probably separators). – Tomasz Gandor Nov 27 '16 at 16:26
  • after checked all the answers, I got my answer: awk is terrible to use. – Siwei Mar 31 '21 at 08:51

19 Answers19

77
awk '{for(i=1;i<4;i++) $i="";print}' file
Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
jiju
  • 795
  • 1
  • 6
  • 2
74

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
Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 14
    probably better to use "NF" than "13" in the last example. – glenn jackman Apr 13 '10 at 14:02
  • 2
    2 scenario that is up to OP to decide. if 13 is the last field, using NF is alright. If not, using 13 is appropriate. – ghostdog74 Apr 13 '10 at 14:07
  • 3
    2nd needs to delete 3 copies of OFS from the start of $0. 3rd would be better with `printf "%s ",$i`, since you don't know whether `$i` might contain `%s` or the like. But that would print an extra space at the end. – dubiousjim Apr 19 '12 at 03:16
56

A solution that does not add extra leading or trailing whitespace:

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 proposes an elegant improvement using the ternary operator 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 gives a solution preserving original whitespaces between fields:

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

BinaryZebra also provides two awesome solutions:
(these solutions even preserve trailing spaces from original string)

$ 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->."

The solution given by larsr in the comments is almost correct:

$ 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

This is the fixed and parametrized version of larsr solution:

$ 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

All other answers before Sep-2013 are nice but add extra spaces:

Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200
  • EdMorton's answer didn't work for me (bash 4.1.2(1)-release,GNU Awk 3.1.7 or bash 3.2.25(1)-release, GNU Awk 3.1.5) but found [here](http://www.cyberciti.biz/faq/unix-linux-bsd-appleosx-skip-fields-command/) another way: `echo ' This is a test' | awk '{print substr($0, index($0,$3))}' ` – elysch Dec 18 '14 at 18:45
  • 1
    @elysch no, that will not work in general, it just appears to work given some specific input values. See the comment I added below your comment under my answer. – Ed Morton Dec 18 '14 at 21:57
  • Good job @BinaryZebra I have just added your awesome solutions linked to your answer. Do you suggest any improvement? Thank you again for your contributions. Cheers :-) *(Note: I will delete this comment in a couple of days to avoid outdated comments that becomes unnecessary for others, you may do the same with your comments)* – oHo Sep 24 '15 at 09:11
  • 1
    Hi @fedorqui. My answer is the first one. In my original answer I was explaining why the other answer were not correct (extra leading or trailing whitespace). Some people have proposed enhancements within comments. We have asked the OP to choose a more correct answer, and he/she has selected mine. After some other contributors have edited my answer to reference there answer (see the history). Is it clear for you? What do you advice me to improve the understandability of my answer? Cheers ;-) – oHo Jul 03 '16 at 14:56
  • 1
    You are absolutely right and I am very sorry for my misunderstanding. I did a fast read to the answer and did not notice your original answer (yes, I read too fast). +1 for the answer itself using the nice trick to loop up to NF-1 and then printing the last element to avoid the extra whitespace. And sorry again! (will remove my comment in a day or so, to prevent misunderstandings from future readers). – fedorqui Jul 04 '16 at 06:56
  • I think many other readers may think as you. Therefore I am wondering how to improve the readability of this answer to ease understanding. What do you suggest? Thanks ;-) – oHo Jul 04 '16 at 08:44
  • 1
    I would use some kind of headers: and then an horizontal rule followed by a big title "comparison of the other answers". Otherwise, move this comparison to another answer, since apparently people tend to prefer short answers in a "gimme my code" vision : ) – fedorqui Jul 04 '16 at 09:30
  • @BinaryZebra's second solution worked the best for me. – RGoodman Oct 21 '22 at 01:56
37

Try this:

awk '{ $1=""; $2=""; $3=""; print $0 }'
lhf
  • 70,581
  • 9
  • 108
  • 149
  • 1
    This is nice because of how dynamic it is. You can add columns at the end and not rewrite your scripts. – MinceMan Jan 13 '12 at 17:09
  • 1
    This demonstrates the exact problem the question is trying to deal with you just do the opposite. What about print the from the 100th field? Note to mention you don't deal with `NF` so you leaving leading `OFS`. – Chris Seymour Sep 15 '13 at 21:17
26

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.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185
  • Didn't work for me (bash 4.1.2(1)-release,GNU Awk 3.1.7 or bash 3.2.25(1)-release, GNU Awk 3.1.5) but found [here](http://www.cyberciti.biz/faq/unix-linux-bsd-appleosx-skip-fields-command/) another way: `echo ' This is a test' | awk '{print substr($0, index($0,$3))}' ` – elysch Dec 18 '14 at 18:47
  • 2
    No, that will fail if $1 or $2 contain the string that $3 is set to. Try, for example `echo ' That is a test' | awk '{print substr($0, index($0,$3))}'` and you'll find that the `a` that is $3 matches the `a` inside `That` in $1. In a very old version of gawk like you have you need to enable RE intervals with the flag `--re-interval`. – Ed Morton Dec 18 '14 at 22:28
  • 2
    You're right, didn't notice. By the way, really appreciate your comment. Many times wanted to use a regex with "{}" to specify number of elements and never saw "--re-interval" in the man. +1 for you. – elysch Dec 19 '14 at 12:48
  • One additional question. Why does the last "1" makes awk to give the changed string instead of the number of changes made by sub? – elysch Dec 19 '14 at 13:10
  • 1
    `1` is a true condition and so invokes the default awk action of printing the current record. – Ed Morton Dec 19 '14 at 13:17
  • Seeing your answer here, I think we need you to post a canonical answer in [delete a column with awk or sed](http://stackoverflow.com/q/15361632/1983854). – fedorqui Jul 01 '16 at 10:44
  • 1
    idk how canonical it is but I added an answer now. – Ed Morton Jul 01 '16 at 12:26
  • @EdMorton supposed I want to apply this only to the third column `$3` to exclude it from the output file? – loretoparisi May 08 '17 at 10:51
  • @loretoparisi please post a new question with it's own [mcve]. – Ed Morton May 08 '17 at 14:08
10

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
Chris Seymour
  • 83,387
  • 30
  • 160
  • 202
6
awk '{$1=$2=$3="";$0=$0;$1=$1}1'

Input

1 2 3 4 5 6 7

Output

4 5 6 7
3

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.

Juan Diego Godoy Robles
  • 14,447
  • 2
  • 38
  • 52
  • This has all the problems @lhf [answer](http://stackoverflow.com/a/2626287/1066031) has.. it's just shorter. – Chris Seymour Sep 15 '13 at 21:19
  • Very good idea ;) Better than my answer! (I have already upvoted your answer last year) Cheers – oHo Apr 07 '14 at 14:18
  • It should be: `awk '{$1=$2=$3=""}sub("^"OFS"+","")' file` as is the OFS what is left after changing $1,$2, and $3 contents. –  Sep 23 '15 at 07:46
3

I can't believe nobody offered plain shell:

while read -r a b c d; do echo "$d"; done < file
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • +1 for the similar solution... But this may have performance issues if `file` is large (>10-30KiB). For large files the `awk` solution performs better. – TrueY Sep 11 '14 at 11:26
3
echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) print $i }'
Vetsin
  • 2,245
  • 1
  • 20
  • 24
  • 3
    Or to get them on the same line, assign $3 to $1, etc. and then change NF to the right number of fields. `echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) $(i-2)=$i; NF=NF-2; print $0 }'` – larsr Apr 24 '12 at 07:48
  • Hi @larsr. Your proposed command line is the single correct answer. All other answers add extra spaces (leading or trailing). Please post your command line within a new answer, I will up-vote it ;-) – oHo Sep 15 '13 at 20:07
  • 1
    Hi @sudo_O, I was speaking to @larsr, about the command line he proposed within his comment. I spent about five minutes before figuring out the quiproco (misunderstanding). I agree, the @Vetsin answer inserts new lines (`ORS`) between fields. Bravo for your initiative (I like your answer). Cheers – oHo Sep 16 '13 at 09:35
  • I would use `echo 1 2 3 4 5| awk '{ for (i=3; i<=NF; i++) printf $i " "; printf "\n" }'` – Culip Mar 30 '21 at 15:29
3

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.

  • Hi BZ Your answer is nice. But Option 3 does not work on string beginning with a space (e.g. `" 1 2 3 4 5 6 7 8 "`). Option 4 is nice but leave a leading space using string beginning with a space. Do you think if this is fixable? You can use command `echo " 1 2 3 4 5 6 7 8 " | your awk script | sed 's/ /./g;s/\t/->/g;s/^/"/;s/$/"/'` in order to verify leading/middle/trailing spaces... Cheers ;) – oHo Sep 24 '15 at 07:46
  • Hi @olibre. That the option 3 fails with white space is the reason to develop options 4 and 5. Option 4 only leaves a leading space if the input has it **and** n is set to 0 (n=0). That I believe is the correct answer when there is no selection of fields (nothing to fix IMO). Cheers. –  Aug 10 '16 at 20:45
  • All right. Thanks for the additional information :-) Please improve your answer providing these extra info :-) Cheers – oHo Aug 11 '16 at 06:39
  • Perfect :-) What a pity your user is disabled :-( – oHo Aug 21 '16 at 12:33
1

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

Chris Koknat
  • 3,305
  • 2
  • 29
  • 30
1

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
$
Michael Back
  • 1,821
  • 1
  • 16
  • 17
  • Can you explain more your answer please ? – Zulu Oct 26 '15 at 19:31
  • Does the edit above help in understanding? The point is to use the complement flag of cut. The solution should be a faster and more concise implementation than AWK or perl based solutions. Also, arbitrary columns can be cut. – Michael Back Oct 26 '15 at 21:52
0

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.

0

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]
Pila
  • 125
  • 1
  • 5
0

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

jgarces
  • 519
  • 5
  • 17
0

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

Shahbaz A.
  • 4,047
  • 4
  • 34
  • 55
zayed
  • 1
  • Seems like your solution might be backward. Please review the question title _Print all *but* the first three columns_ – Stefan Crain Apr 01 '19 at 17:22
-1

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.

user2141650
  • 2,827
  • 1
  • 15
  • 23
-1

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) }'
$
Michael Back
  • 1,821
  • 1
  • 16
  • 17