5

I have this minimal awk example:

#!/usr/bin/awk -f
BEGIN {
  if (ARGC>1)
    a=ARGV[1]
  else
    a=5
}

{
  print
}

Calling it with ls -l|./t.awk, results in:

$ ls -l|./t.awk 2
awk: ./t.awk:4: fatal: cannot open file `2' for reading (No such file or directory)

When calling it without arguments (ls -l|./t.awk), there is no problem.

Thus, my first problem is that instead of a simple string equality check, somehow it tries to read in the file named "2".

The second surprising thing is that removing the second block makes it work as it should! (Giving empty output.)

#!/usr/bin/awk -f
BEGIN {
  if (ARGC>1)
    a=ARGV[1]
  else
    a=5
}

What is the cause?

terdon
  • 242,166
peterh
  • 9,731

2 Answers2

5

Though awk presents you with the arguments, it is up to you to manipulate them into what you finally wish to have. Eg, just set the arg empty:

if (ARGC>1) {a=ARGV[1]; ARGV[1]=""}
else a=5

Otherwise, the args have their normal function, which for non-options is to be input filenames. So awk will try to read file "2" and run the body of the script on it.

meuh
  • 51,383
  • I believe the missing info what you forgot to mention, that awk wants to interpret my "2" as the next script? If yes, why causes it error only at the nameless block? – peterh May 08 '20 at 14:16
  • I inserted it into your post, feel free to revert/extend if it was not okay. – peterh May 08 '20 at 14:20
  • 3
    I do not accept that extra args are used as additional source files. They are data files. GNU Awk Users Guide says if you use an element of ARGV as your own arg, then you have to null it to avoid it being used as a file too. The reason omitting the action block fixes the issue is that gawk is smart enough not to read any input if the script body or END block are not there, because nothing else will happen anyway.. – Paul_Pedant May 08 '20 at 14:33
  • @Paul_Pedant 've removed the poster's erroneous edit. thanks. – meuh May 08 '20 at 14:41
  • @meuh Then I will write a correct self-answer, if this question won't be deleted. – peterh May 08 '20 at 14:55
  • @Paul_Pedant Well, I did not check the gawk source yet, but I am sure that it (the now removed part) is the reason. Your post is in its current form a non-answer, because it does not explain the cause of the problem. – peterh May 08 '20 at 15:00
  • 1
    @peterh-ReinstateMonica there is no problem here, awk is working exactly as it should: by default, awk expects files as arguments, if you want something else you need to handle it. – terdon May 08 '20 at 15:46
  • @peterh : I can read the GNU Awk User Guide, in preference to the code. Specifically on ARGV "To eliminate a file from the middle of the list" (explains why 2 is read as a file), and on BEGIN "If an awk program has only BEGIN rules and no other rules" (explains why it does not read 2 when there the body action is removed). So my comment is correct. and explains all your issues, and was not posted as an answer. – Paul_Pedant May 08 '20 at 18:12
2

When you pass an argument to awk on the command line, it assumes you are giving it a file, so it tries to open it. I don't really get what you were expecting to happen, this is how awk normally behaves.

The reason it works if you remove the { print } is also quite straightforward. If you remove it, you are left with nothing but a BEGIN{} block, which is run before any input files are read. Since there's nothing apart from the BEGIN{}, there is no reason to even try to read a file, and the program exits. This can be easily confirmed with strace:

$ strace awk 'BEGIN{a=1}{print}' somefile |& grep 'open.*somefile'
openat(AT_FDCWD, "somefile", O_RDONLY)  = -1 ENOENT (No such file or directory)
write(2, "cannot open file `somefile' for "..., 66cannot open file `somefile' for reading: No such file or directory) = 66

Compare the above to:

$ strace awk 'BEGIN{a=1}' somefile |& grep 'open.*somefile'
$ 

Since there's nothing to be done with the file, there is no attempt made to open it.


What I think you are looking for is the foo=bar format which lets you pass a variable at the command line:

ls -l|./t.awk a=2

Next, change your script to this:

#!/bin/awk -f
BEGIN {
  if (!a){
    a=5
  }
}

{
  print a,$0
}

And to illustrate how it works:

$ echo foo | t.awk          ## default value
5 foo
$ echo foo | foo.awk a=12   ## value given at launch
12 foo

Alternatively, when running directly from the command line, you can also use the -v option to do the same thing:

$ awk -v a=12 'BEGIN{print a}'
12
terdon
  • 242,166