29

I want to print all columns from nth to last column of a line

Input String in file

vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3  

Command

cat <file> | awk ' { for (i=3; i<=NF; i++)   print $i }'

Current Output

cb0
cb1
cb2
cb3
ct0
ct1
ct2
ct3

Desired Output

cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3

I am trying the awk iteration, but cannot get desired output

1
  • You almost got it. You may change print $i to printf $i" ". Full command looks like echo vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3 | awk ' { for (i=3; i<=NF; i++) printf $i" " }' Commented Mar 17, 2021 at 17:35

12 Answers 12

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

This will take n as the value of n and loop through that number through the last field NF, for each iteration it will print the current value, if that is not the last value in the line it will print OFS after it (space), if it is the last value on the line it will print ORS after it (newline).

$ echo 'vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3' |
> awk -v n=4 '{ for (i=n; i<=NF; i++) printf "%s%s", $i, (i<NF ? OFS : ORS)}'
cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3
18

Doesn't answer your question using the awk approach requested, but I'll just point out that cut can be employed for this task.

cut -f4- -d' ' input
1
  • 10
    cut expects one and only one SPC between fields and doesn't ignore leading and trailing ones, while awk ignores leading SPC+TABs (sometimes other [:blank:], sometime including newline, sometimes all of ASCII [:space:]) and treats sequences of one or more whitespace as delimiter. Commented Jan 12, 2020 at 8:12
8

command

awk '{$1=$2=$3="";print $0}' filename| sed -r "s/^\s+//g"

command

cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3
5
  • 14
    awk '{$1=$2=$3=""; $0=$0; $1=$1; print}' file
    – RudiC
    Commented Jan 12, 2020 at 12:28
  • 1
    @RudiC you should make that an answer -- it's much better than your answer above ;-)
    – user313992
    Commented Jan 12, 2020 at 16:44
  • @mosvy : yes, thank you - but it is not mine; don't want to steal an idea.
    – RudiC
    Commented Jan 12, 2020 at 19:05
  • 1
    The best and the shortest answer found so far awarded to @RudiC
    – MaXi32
    Commented Oct 11, 2020 at 15:41
  • @RudiC am I wrong in thinking you don't need the $0=$0 and the $1=$1? Assigning any value to any of the fields (as you do when you set the first 3 to empty string) is supposed to rebuild the whole line, so $0 should have the correct value already. Seems to work that way in my tests.
    – philraj
    Commented Nov 16, 2020 at 21:51
7

If you want to preserve the spacing in between the fields that are going to be preserved, you might as well do:

<file awk '{sub(/^[[:blank:]]*([^[:blank:]]+[[:blank:]]+){3}/, ""); print}'

(though note that not all awk implementations support {3} even though it's POSIX).

That is use a regular expression that matches the first 3 fields.

Or simply use sed to do that:

<file sed 's/^[[:blank:]]*\([^[:blank:]]\{1,\}[[:blank:]]\{1,\}\)\{3\}//'

Another approach with sed is:

<file sed 's/[^[:blank:]]\{1,\}[[:blank:]]*/\
/3; s/.*\n//'

Which replaces the 3rd field and the blanks following it with a newline and then deletes everything up to that newline.

Those assume that all the lines in the input have at least 4 fields.

On a a b c d <tab>e f <nl> input, that will give d <tab>e f <nl> output.

2
  • ...and quietly lurking in the forest was the right answer :-). The awk command should be using sub() instead of gsub() but no real harm done other than to make some of us pause to think about why it's using gsub()!
    – Ed Morton
    Commented Jan 13, 2020 at 3:52
  • 1
    @EdMorton Thanks. good point about gsub. I've replaced it with sub. Commented Jan 13, 2020 at 7:14
4

An improvement on the $1=$2=$3="" trick which works with any FS or OFS, provided that normalizing the output line to $4 OFS $5 OFS ... is acceptable:

awk 'BEGIN{p=length(OFS)*3+1} {$1=$2=$3="";print substr($0,p)}'

Assigning a value to any field (eg. $1="" or $1=$1) will cause $0 (the whole record/line) to be rebuilt (the first time it is used) by joining the $1, $2, ... fields with the OFS variable.

So $1 = $2 = $3 = "" will have the effect of setting $0 to OFS OFS OFS $4 OFS $5 ..., and the substr will get rid of leading OFSs (notice that indexes start from 1 in awk).

1
  • Collapses blank space between fields.
    – user232326
    Commented Jan 13, 2020 at 16:50
4

cut could get you the same result, for example

echo "one two three four" | cut -d" " -f 2-

this will give you two three four, if you were concerned about leading and trailing spaces you could squeezed the spaces with tr -s " " for example

echo "one    two  three  four   " | cut -d" " -f 2-

will give you the same result

3

with awk that supports regex in field separator, you can do:

awk -F'^[[:blank:]]*([^ \t]*[ \t]+){3}' '{ print $2 }' infile
cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3

replace number 3 in {3} with number of columns you need ignore from the beginning; and space/tab with field separator which your columns delimited with other than whitespaces; so printing $2 would be the rest of columns remaining to the end; this solution will preserve multiple whitespaces between fields if any in result.

notes: to avoid printing empty line if there was a line with equal or less than 3 fields (that indicates the # (number) of columns you need cut out) , add NF>1 to the command.

awk -F'^[[:blank:]]*([^ \t]*[ \t]+){3}' 'NF>1{ print $2 }' infile

[[:blank:]]* is used to avoid erroneous output when FS is whitespaces; if FS is something else (e.x: comma ,), then the command would be even simpler:

awk -F'^([^,]*,){3}' 'NF>1{ print $2 }' infile
3
  • Every awk supports a regexp for FS (it's mandated by POSIX and even non-POSIX awks support it). Only gawk supports \s shorthand for [[:space:]] though so you should change that or just say its gawk-only. If you keep it then it doesn't make sense to use \s in one place and [ \t] in another so you can replace the [ \t] with \\s and [^ \t] with \\S.
    – Ed Morton
    Commented Jan 13, 2020 at 3:57
  • thanks I have edited with your suggestion but [[:blank:]] instead of [[:space:]] to match also tab as well as space, but I kept using [ \t] amd [^ \t] since they are representing field seperator and it's to understand where to modify if one's field seperator is something other than whitespaces and only need change ` \t` part there Commented Jan 13, 2020 at 4:26
  • You're welcome. If the OP changes to use something other than the default FS then you'd have to get rid of the leading [[:blank:]]* too.
    – Ed Morton
    Commented Jan 13, 2020 at 4:29
2

Perl solution:

space-delimited:

perl -lane 'print "@F[3..$#F]"' file

tab-delimited:

perl -F'\t' -lane 'print join "\t", @F[3..$#F]' file

Here, the command-line flags are:
-e: tells perl interpreter to use the inline code rather than code in file.
-n: loops over the input one line at a time, assigning the input line to $_.
-l: strip the input line separator (new line in UNIX) before the code in the one-liner, and add the output line separator after the print.
-a: split each line in array @F, on whitespace by default.
-F: split on the provided pattern, rather than on whitespace.

@F[3..$#F]: fields with indexes from 3 to $#F (the last element of @F), indexes are zero-based.

1
  • 1
    if you're to use perl instead of awk, you can use split with a limit and easily preserve the original separators: perl -nle 'print((split " ", $_, 4)[3])' or perl -pe '$_ = (split " ", $_, 4)[3]'
    – user313992
    Commented Jan 12, 2020 at 17:14
1

How about

awk 'sub ("^" $1 FS $2 FS $3 FS, _)' file
cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3  
2
  • Doesn't work with FS=' ' (the default) when the fields are separated by multiple spaces or tabs or combinations thereof. Also fails with FS='*' or FS='.'.
    – user313992
    Commented Jan 12, 2020 at 16:19
  • 1
    also since first parameter of sub is regex so it will fail if one of columns 1 or 2 or 3 was containing characters other than alpha/num/alnum; for example when a line starts with [0-9] Commented Jan 12, 2020 at 16:40
1

Tried in Python 2:

#!/usr/bin/python
k=open('filename','r')
for i in k:
    k=i.strip().split(' ')[3:]
    print " ".join(k)

output

cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3
1

print insert a newline by default. Use printf instead of print

echo "vddp vddpi vss cb0 cb1 cb2 cb3 ct0 ct1 ct2 ct3" | awk ' { for (i=4; i<=NF; i++) printf "%s " ,$i }'

For multiple lines:

cat filename.txt | awk ' { for (i=4; i<=NF; i++) 
if (i==NF) 
printf "%s\n", $i
else
printf "%s ", $i
}'
1
  • Didn't thought about multiple lines. Added the code for multiple line. Commented Jan 13, 2020 at 15:33
1

Retain white space after the field(s) removed:

$ sed -E 's/^[[:blank:]]*([^[:blank:]]+[[:blank:]]*){3}//' file
$ awk '{sub(/^[[:blank:]]*([^[:blank:]]+[[:blank:]]*){3}/,"")};1' file
$ grep -oP '^[[:blank:]]*([^[:blank:]]+[[:blank:]]*){3}\K.*' file

Description

There is a way to match fields with a regex: [[:blank:]]*[^[:blank:]]+.
Match an optional stream of blanks (space or tab) followed by an stream of non-blanks. If that pattern is repeated 3 times, 3 fields (with the leading white space) are matched. Then, those could be removed with any tool that is able to process regexes (with variations). Note that there may be white space before and/or after each field, that white space needs to be also matched. Matching any leading white space and the delimiting trailing white space will make the regex a bit longer: ^[[:blank:]]*([^[:blank:]]+[[:blank:]]*){n} where n is an integer that points to the last field matched.

Alternative with GNU awk:

$ awk -vrem=3 '{ 
                 n=split($0,f,FS,seps);
                 for(i=rem+1;i<=n;i++){
                                        printf("%s%s",$i,seps[i])
                                      };
                 print ""
               }
              '  file

White space collapsed

$ awk '{$1=$2=$3=""; $0=$0; $1=$1 } 1 ' file      # thanks RudiC

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .