Picking input record fields with AWK

Viewed 111

Let's say we have a shell variable $x containing a space separated list of numbers from 1 to 30:

$ x=$(for i in {1..30}; do echo -n "$i "; done)
$ echo $x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

We can print the first three input record fields with AWK like this:

$ echo $x | awk '{print $1 " " $2 " " $3}'
1 2 3

How can we print all the fields starting from the Nth field with AWK? E.g.

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

EDIT: I can use cut, sed etc. to do the same but in this case I'd like to know how to do this with AWK.

5 Answers

Converting my comment to answer so that solution is easy to find for future visitors.

You may use this awk:

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

or pass start position as argument:

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

Version 4: Shortest is probably using sub to cut off the first three fields and their separators:

$ echo $x | awk 'sub(/^ *([^ ]+ +){3}/,"")'

Output:

4 5 6 7 8 9 ...

This will, however, preserve all space after $4:

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

so if you wanted the space squeezed, you'd need to, for example:

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

with the exception that if there are only 4 fields and the 4th field happens to be a 0:

$ echo "1 2 3 0" | awk 'sub(/^ *([^ ]+ +){3}/,"")&&$1=$1'
$ [no output]

in which case you'd need to:

$ echo "1 2 3 0" | awk 'sub(/^ *([^ ]+ +){3}/,"") && ($1=$1) || 1'
0

Version 1: cut is better suited for the job:

$ cut -d\  -f 4- <<<$x

Version 2: Using awk you could:

$ echo -n $x | awk -v RS=\  -v ORS=\  'NR>=4;END{printf "\n"}'

Version 3: If you want to preserve those varying amounts of space, using GNU awk you could use split's fourth parameter seps:

$ echo "1 2 3 4  5   6    7" | 
gawk '{
    n=split($0,a,FS,seps)                     # actual separators goes to seps
    for(i=4;i<=n;i++)                         # loop from 4th
        printf "%s%s",a[i],(i==n?RS:seps[i])  # get fields from arrays
}'

Adding one more approach to add all value into a variable and once all fields values are done with reading just print the value of variable. Change the value of n= as per from which field onwards you want to get the data.

echo "$x" |
awk -v n=3 '{val="";for(i=n; i<=NF; i++){val=(val?val OFS:"")$i};print val}'

With GNU awk, you can use the join function which has been a built-in include since gawk 4.1:

x=$(seq 30 | tr '\n' ' ')

echo "$x" | gawk '@include "join"   
                {split($0, arr)
                print join(arr, 4, length(arr), "|")}
                '
4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30

(Shown here with a '|' instead of a ' ' for clarity...)

Alternative way of including join:

echo "$x" | gawk -i join '{split($0, arr); print join(arr, 4, length(arr), "|")}'

Using gnu awk and gensub:

echo $x | awk '{ print gensub(/^([[:digit:]]+[[:space:]]){3}(.*$)/,"\\2",$0)}'

Using gensub, split the string into two sections based on regular expressions and print the second section only.

Related