6

I have a file of patterns and I want to return all the line numbers where the pattern was found, but in a wide format and not long/spread. Example:

fileA.txt

Germany
USA
UK

fileB.txt

USA
USA
Italy
Germany
UK
UK
Canada
Canada
Germany
Australia
USA

I have done something like this:

grep -nf fileA.txt fileB.txt

which returned me:

1:USA
2:USA
4:Germany
5:UK
6:UK
9:Germany
11:USA

However, I want to have something like:

Germany 4 9
USA 1 2 11
UK 5 6
2
  • What should be output for a country that doesn't appear in fileB.txt?
    – Ed Morton
    Feb 14 at 12:21
  • @EdMorton I think empty values in that case I guess, my patterns are all present in fileB Feb 14 at 15:30
11

Using GNU datamash:

$ grep -n -x -F -f fileA.txt fileB.txt | datamash -s -t : -g 2 collapse 1
Germany:4,9
UK:5,6
USA:1,2,11

This first uses grep to get the lines from fileB.txt that exactly matches the lines in fileA.txt, and outputs the matching line numbers along with the lines themselves.

I'm using -x and -F in addition to the options that are used in the question. I do this to avoid reading the patterns from fileA.txt as regular expressions (-F), and to match complete lines, not substrings (-x).

The datamash utility is then parsing this as lines of :-delimited fields (-t :), sorting it (-s) on the second field (-g 2; the countries) and collapsing the first field (collapse 1; the line numbers) into a list for each country.

You could then obviously replace the colons and commas with tabs using tr ':,' '\t\t', or with spaces in a similar way.

$ grep -n -x -f fileA.txt -F fileB.txt | datamash -s -t : -g 2 collapse 1 | tr ':,' '\t\t'
Germany 4       9
UK      5       6
USA     1       2       11
0
8

Use awk:

awk 'NR==FNR        { country[$0]= country[$0]? country[$0] FS NR: NR; next }
     ($0 in country){ print $0, country[$0] }' fileB fileA

or to report "count: 0" in case there was a countryName in fileA that doesn't appear in fileB, do:

awk 'NR==FNR        { country[$0]= country[$0]? country[$0] FS NR: NR; next }
     ($0 in country){ print $0, country[$0]; next } { print $0, "0" }' fileB fileA
5

You could couple your grep command output with Miller (https://github.com/johnkerl/miller) and run

grep -nf fileA.txt fileB.txt | \
mlr --c2n --ifs ":" --implicit-csv-header --headerless-csv-output reorder -f 2  then \
nest --implode --values --across-records --nested-fs " " -f 1

You will have

Germany 4 9
USA 1 2 11
UK 5 6
2

Using an awk array, which is associative by definition, as a dictionary/map to collect the indexes and then dump all the keys and values in it (also using Kusalananda additions to the grep command):

$ grep -n -x -F -f fileA.txt fileB.txt \
| awk -F: '{dict[$2]=dict[$2]" "$1} END {for (key in dict) print key dict[key]}'
UK 5 6
Germany 4 9
USA 1 2 11
2
$ grep -nxFf fileA.txt you fileB.txt   \
| awk -F: '$0 = (length($2) FS $0)' \
| sort -t: -k1,1nr -k3,3 -k2,2n \
| cut -d: -f2- \
| sed -Ee '
  :a
    $!N;y/:/ /
    s/( \S+)\n(.*\1)$/ \2/
  ta
  s/([^\n]*) ([^\n]*)((\n.*)?)$/\2 \1\3/
  P;D
'

O/P:

Germany 4 9
USA 1 2 11
UK 5 6

Note: this requires GNU sed to be able to run as the \S and \n in char classes is used.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.