0
\$\begingroup\$

I just recently started using AWK and I'm still learning about it. I have solved the problem I'm about to show but I feel it's not the best solution and I'm trying to find a solution to fit within an AWK command rather than keep piping.

I have a bunch of .txt files ending for which I read the header (1st line only).

head -1 *.txt 

Outputs:

==> anglia.txt <== 
String - Anglian            

==> carr.txt <==   
String - Carr               
etc..

From here I have a case switch where a user inputs 1-9. It's sorted so first line is always Anglian and second is always Carr.. etc So if a user inputs 1 I know they want to select anglian. But for further process my code I need to extract the String "Anglian".

head -1 *.txt | awk '/[a-z]/&&!/.txt/' 

Outputs:

String - Anglian
String - Carr

Here I got rid of the first line that had the filename.

head -1 *.txt | awk '/[a-z]/&&!/.txt/' | awk '{print $3}'

Outputs:

Anglian
Carr

Here I selected column 3 which contains the String that I need!

head -1 *.txt | awk '/[a-z]/&&!/.txt/' | awk 'NR==1{print $3}'

Outputs:

Anglian

Here I selected the first row which is exactly the output that I want! However I had to use an extra pipe. All I want is a awk command that does all of this in a single command somethings around the lines of:

head -1 *.txt | awk '/[a-z]/&&/.txt/{if(NR==1)print $3}'

Outputs:

EMPTY LINE

This is the problem I'm having. Since I'm having the pattern and NR in 1 command it's selecting the 1st line but the first line is a line that is being hidden by my pattern and it's the ==> anglian.txt <== hence

head -1 *.txt | awk '/[a-z]/&&/.txt/{if(NR==2)print $3}'

Outputs:

Anglian

However this is of no use because only matches Anglian where it is NR==2 and due to my case switch that I have I want it to be NR==1 otherwise the code does not work.

Is this possible? I hope I made myself clear here :)

\$\endgroup\$
  • 1
    \$\begingroup\$ I'd like you help you, but you need more information about the files. I think that $3 on line1 is "Anglican" or one of the other keys. And that word maps to a line number, and then you want to print $3 from that line number. Is that right? Please demonstrate by including a couple of your files. \$\endgroup\$ – glenn jackman Mar 26 at 17:30
  • \$\begingroup\$ (Welcome to Code Review!) I just recently started using AWK and I'm still learning about it Why? It was quite something when it was new. Then, there was new awk, but the world keeps turning: contemporary contenders include Perl (5+), Python (3.3+, 2.7.1 for Jython), Ruby… \$\endgroup\$ – greybeard Mar 26 at 21:55
  • \$\begingroup\$ Please include an example of the output of head -1 *.txt. Without that, I can't imagine why $3 would be printed, and I have a hard time trying to imagine the code presented works as intended. \$\endgroup\$ – greybeard Mar 26 at 22:03
  • \$\begingroup\$ @greybeard I work for a company that provides the billing system to BT in the UK and we do a lot of data mediation; awk seems to be used quite often for data formatting... So i'm trying to learn it to try and understand some of the scripts we have implemented, etc although some are quite old. \$\endgroup\$ – zuNEC Mar 27 at 9:22
  • \$\begingroup\$ So you look at the first line of a file to get a keyword. You use that keyword to get a line number. What file is that line number used for? The same file? Show your input files (or a short representation of them) and your desired output. \$\endgroup\$ – glenn jackman Mar 27 at 12:11
0
\$\begingroup\$

You can replace this pipeline

head -1 *.txt | awk '/[a-z]/&&!/.txt/' | awk '{print $3}'

with this one awk command

awk 'FNR == 1 {print $3}' *.txt

FNR is the file record (i.e. line) number. NR is the cumulative record number of all records seen from all files.


Now, you can select the user's numeric choice with

awk 'FNR == 1 {print $3}' *.txt | awk -v n="$user_selection" 'NR == n'

or, with a single awk:

awk -v n="$user_selection" 'FNR == 1 && ++filenum == n {print $3; exit}' *.txt

If you're looking for a way to get your users to select a name from one of the files, perhaps some more advanced bash:

# read the 3rd word from the 1st line of all txt files into an array
readarray -t names < <(awk 'FNR == 1 {print $3}' *.txt)

# get the user to select one of them
PS3="Choose a name: "
select name in "${names[@]}"; do
    [ "$name" ] && break
done

echo "$name"

This really isn't a code review. Since you're asking "how can I do this", you should have asked on Stack Overflow.

\$\endgroup\$
  • \$\begingroup\$ awk -v n="$user_selection" 'FNR == 1 && ++filenum == n {print $3; exit}' *.txt worked wonders :) many thanks. Also I was in stack overflow but since I had a "solution" to the problem they told me to come here. Many thanks! \$\endgroup\$ – zuNEC Mar 27 at 16:31
0
\$\begingroup\$

I'm afraid you can't do this like that, because NR is a number of an input record, not a number of records matched so far. Even if it was, you need to count records matched by the first clause of the condition but actually not yet printed due to the second clause.

Probably the good way to do that is explicit counting:

BEGIN { cnt = N }
/[a-z]/&&!/.txt/ { if (cnt-- == 0) print  $3 }

I am not sure, however, how to pass an argument from the command line to the script's variable N.

\$\endgroup\$
  • \$\begingroup\$ If you do this inline instead of it's own awk script file I think you can just use Bash's variable expansion (but be sure to escape the dollar sign later in the code). Or just hard code it! \$\endgroup\$ – Captain Man Mar 26 at 20:19

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.