fatal: function name previously defined - gawk

For the listing below, I get an error:
fatal: function name `myprint' previously defined
$3 > 0 { myprint ($3) }
function myprint(num)
{
printf "%6.3g\n", num
}

I discovered that I had an extra space after the call and between the parameters:
myprint ($3)
should be:
myprint($3)

Related

How to use the eval statement in (g)awk?

Due to that awk does not seem to have callbacks, I was planning to use the eval statement for this. So I had a look at the GNU user guide,
https://www.gnu.org/software/gawk/manual/html_node/Viewing-And-Changing-Data.html
and then wrote this simple script.
BEGIN {
args[1]="\"a\""
args[2]="\"b\""
args[3]="\"c\""
args[4]="\"\""
run_callback("printargs",args)
print args[4]
}
function run_callback(callback,args)
{
nargs=length(args)
if (nargs>0)
{
argstring=args[1]
for (argn=2;argn<=nargs;argn++)
{
argstring=argstring","args[argn]
}
}
callbackstr = callback"("argstring")"
print callbackstr
eval callbackstr
}
function printargs(arg1,arg2,arg3,res)
{
res=arg1","arg2","arg3
print "res="res
}
However, the printout is not what I expected. I get this,
[~]-> gawk -f callback.awk
printargs(a,b,c,"")
""
And not the expected,
[~]-> gawk -f callback.awk
printargs(a,b,c,"")
res=a,b,c
"Not sure what is supposed to be here, but it is not relevant."
It feels as if nothing actually happens inside the eval statement. Anyone who knows what happens here?
gawk version is 4.1.3
BR
Patrik
That's in the documentation for the gawk debugger. It's not a normal gawk function.
However, gawk does support calling a function whose name is in a string with the #var(args,...) notation (More information in the documentation):
BEGIN {
args[1]="a"
args[2]="b"
args[3]="c"
args[4]="\"\""
run_callback("printargs",args[1],args[2],args[3],args[4])
print args[4]
}
function run_callback(callback,arg1,arg2,arg3,res)
{
#callback(arg1,arg2,arg3,res);
}
function printargs(arg1,arg2,arg3,res)
{
res=arg1","arg2","arg3
print "res="res
}
when run will print out
res=a,b,c
""
Note that args[4] isn't modified from this. From the documentation on function argument passing convention:
Instead, the passing convention is determined at runtime when the function is called, according to the following rule: if the argument is an array variable, then it is passed by reference. Otherwise, the argument is passed by value.
If you passed args directly and modified elements of it in the callback, you'd see the changes reflected.
awk doesn't have any eval keyword.
This can be check with gawk --dump-variables option
gawk --dump-variables -f callback.awk
It outputs the file awkvars.out and you'll find in it:
eval: uninitialized scalar

AWK Curry Function - Can a function return a function?

I'm working on an AWK script that quite a big mess at the moment, and I'm trying to improve it (primarily because I want to improve my Awk scripting skillz)
I realize that there isn't a way to do Object Oriented Programming within Awk or Gawk, but is there a way to at least curry functions? As in returning a function from within a function? (Not return the result of the executed function, but rather return a function that can be executed)
I found a Stack Overflow post, where #GreenFox showed that its possible to execute a function with the name of the function stored in a variable. The example he posted is below:
function foo(s){print "Called foo "s}
function bar(s){print "Called bar "s}
{
var = "";
if(today_i_feel_like_calling_foo){
var = "foo";
}else{
var = "bar";
}
#var( "arg" ); # This calls function foo(), or function bar() with "arg"
}
What I'm wondering is if its possible to return a function from another function.
For example, a function that accepts a string that can be used in awks printf as a format, and returns a function that accepts two other arguments, and essentially executes printf( fmt_from_parent_func, sub_func_arg1, sub_func_arg2 ).
Here's my attempt at trying to accomplish the following:
#! /usr/local/bin/awk -f
function setFmt ( fmt ){
function _print ( var, val ){
printf ( fmt ? fmt : "%-15s: %s\n" ), str1, str2
}
return #_print
}
BEGIN {
fmtA = setFmt("%-5s: %s\n")
#fmtA("ONE","TWO")
}
Which results in the errors:
awk: ./curry.awk:4: function _print ( var, val ){
awk: ./curry.awk:4: ^ syntax error
awk: ./curry.awk:4: function _print ( var, val ){
awk: ./curry.awk:4: ^ syntax error
awk: ./curry.awk:6: printf ( fmt ? fmt : "%-15s: %s\n" ), str1, str2
awk: ./curry.awk:6: ^ unexpected newline or end of string
awk: ./curry.awk:11: fmtA = setFmt("%-5s: %s\n")
awk: ./curry.awk:11: ^ unexpected newline or end of string
awk: ./curry.awk:12: #fmtA("ONE","TWO")
awk: ./curry.awk:12: ^ unexpected newline or end of string
If anyone knows if this is at all possible (which Im starting to see myself), and knows a way to accomplish something to this effect.. that would be awesome.
Thanks!
With GNU awk you can return the name of a function as a string from another function but you can't declare a function within another function nor can you return a function (or an array) in any awk - all you can return from a function in awk is a scalar value (i.e. a number or string).
Is this what you're trying to do:
$ cat tst.awk
function _print ( var, val ){
printf _print_fmt, var, val
}
function setFmt ( fmt ){
_print_fmt = (fmt ? fmt : "%-15s: %s\n" )
return "_print"
}
BEGIN {
fmtA = setFmt("%-5s: %s\n")
#fmtA("ONE","TWO")
}
$ awk -f tst.awk
ONE : TWO

Use of uninitialized value in concatenation (.) or string at

I've this error:
Use of uninitialized value in concatenation (.) or string at...
This is my code:
$queryH= $dbh->prepare($query);
$queryH->execute();
my $i=0;
my $result;
while (#data = $queryH->fetchrow_array())
{
$result=$data[$i];
if ($result)
{
print "$result \n";
}
else
{
print "Records Not Found!\n";
last;
}
print "\n";
$i++;
}
print "\n\n";
$queryH->finish();
$dbh->disconnect();
What error is there in my code?
The error is on the line:
$result=$data[$i];
You are starting out with $i=0 and you increment it with each pass of the while loop. You look at $data[$i] once for each line of database result. So for the first row, you look at $data[0]. For the second row of data, you look at $data[1] and so on. At some point, your $i (which is effectively a rowcount starting at zero) will be higher than the number of fields per row. That produces the error message.
If you put in a print "$i\n" in the beginning of the while block you will see what I am talking about.

awk 1 unexpected character '.' suddenly appeared

the script was working. I added some comments and renamed it then submitted it. today my instructor told me it doesnt work and give me the error of awk 1 unexpected character '.'
the script is supposed to read a name in command line and return the student information for the name back.
right now I checked it and surprisingly it gives me the error.
I should run it by the command like this:
scriptName -v name="aname" -f filename
what is this problem and which part of my code make it?
#!/usr/bin/awk
BEGIN{
tmp=name;
nameIsValid;
if (name && tolower(name) eq ~/^[a-z]+$/ )
{
inputName=tolower(name)
nameIsValid++;
}
else
{
print "you have not entered the student name"
printf "Enter the student's name: "
getline inputName < "-"
tmp=inputName;
if (tolower(inputName) eq ~/^[a-z]+$/)
{
tmpName=inputName
nameIsValid++
}
else
{
print "Enter a valid name!"
exit
}
}
inputName=tolower(inputName)
FS=":"
}
{
if($1=="Student Number")
{
split ($0,header,FS)
}
if ($1 ~/^[0-9]+$/ && length($1)==8)
{
split($2,names," ")
if (tolower(names[1]) == inputName || tolower(names[2])==inputName )
{
counter++
for (i=1;i<=NF;i++)
{
printf"%s:%s ",header[i], $i
}
printf "\n"
}
}
}
END{
if (counter == 0 && nameIsValid)
{
printf "There is no record for the %-10s\n" , tmp
}
}
Here are the steps to fix the script:
Get rid of all those spurious NULL statements (trailing semi-colons at the end of lines).
Get rid of the unset variable eq (it is NOT an equality operator!) from all of your comparions.
Cleanup the indenting.
Get rid of that first non-functional nameIsValid; statement.
Change printf "\n" to the simpler print "".
Get rid of the useless ,FS arg to split().
Change name && tolower(name) ~ /^[a-z]+$/ to just the second part of that condition since if that matches then of course name is populated.
Get rid of all of those tolower()s and use character classes instead of explicit a-z ranges.
Get rid of the tmp variable.
Simplify your BEGIN logic.
Get rid of the unnecessary nameIsValid variable completely.
Make the awk body a bit more awk-like
And here's the result (untested since no sample input/output posted):
BEGIN {
if (name !~ /^[[:alpha:]]+$/ ) {
print "you have not entered the student name"
printf "Enter the student's name: "
getline name < "-"
}
if (name ~ /^[[:alpha:]]+$/) {
inputName=tolower(name)
FS=":"
}
else {
print "Enter a valid name!"
exit
}
}
$1=="Student Number" { split ($0,header) }
$1 ~ /^[[:digit:]]+$/ && length($1)==8 {
split(tolower($2),names," ")
if (names[1]==inputName || names[2]==inputName ) {
counter++
for (i=1;i<=NF;i++) {
printf "%s:%s ",header[i], $i
}
print ""
}
}
}
END {
if (counter == 0 && inputName) {
printf "There is no record for the %-10s\n" , name
}
}
I changed the shebang line to:
#!/usr/bin/awk -f
and then in command line didnt use -f. It is working now
Run the script in the following way:
awk -f script_name.awk input_file.txt
This seems to suppress the warnings and errors.
In my case, the problem was resetting the IFS variable to be IFS="," as suggested in this answer for splitting string into an array. So I resetted the IFS variable and got my code to work.
IFS=', '
read -r -a array <<< "$string"
IFS=' ' # reset IFS

how to create an empty array

UPDATE
The original description below has many errors; gawk lint does not complain about uninitialized arrays used as RHS of in. For example, the following example gives no errors or warnings. I am not deleting the question because the answer I am about to accept gives good suggestion of using split with an empty string to create an empty array.
BEGIN{
LINT = "fatal";
// print x; // LINT gives error if this is uncommented
thread = 0;
if (thread in threads_start) {
print "if";
} else {
print "not if";
}
}
Original Question
A lot of my awk scripts have a construct as follows:
if (thread in threads_start) { // LINT warning here
printf("%s started at %d\n", threads[thread_start]));
} else {
printf("%s started at unknown\n");
}
With gawk --lint which results in
warning: reference to uninitialized variable `thread_start'
So I initialize in the BEGIN block as follows. But this looks kludge-y. Is there a more elegant way to create a zero-element array?
BEGIN { LINT = 1; thread_start[0] = 0; delete thread_start[0]; }
I think you might have made a few typo's in your code.
if (thread in threads_start) { // LINT warning here (you think)
Here you look for the index thread in array threads_start.
printf("%s started at %d\n", threads[thread_start])); // Actual LINT warning
But here you print the index thread_start in array threads! Also notice the different s's thread/threads and threads_start/thread_start. Gawk is actually warning you correctly about the usage of thread_start (without s) on the second line.
There also is an error in your printf format.
When you change these the lint warning disappears:
if (thread in threads_start) {
printf("%s started at %d\n", thread, threads_start[thread]));
} else {
printf("%s started at unknown\n");
}
But perhaps I've misunderstood what your code is supposed to do. In that case, could you post a minimal self-contained code sample that produces the spurious lint warning?
Summary
The idiomatic method of creating an empty array in Awk is to use split().
Details
To simplify your example above to focus on your question rather than your typos, the fatal error can be triggered with:
BEGIN{
LINT = "fatal";
if (thread in threads_start) {
print "if";
} else {
print "not if";
}
}
which produces the following error:
gawk: cmd. line:3: fatal: reference to uninitialized variable `thread'
Giving thread a value before using it to search in threads_start passes linting:
BEGIN{
LINT = "fatal";
thread = 0;
if (thread in threads_start) {
print "if";
} else {
print "not if";
}
}
produces:
not if
To create a linting error with an uninitialised array, we need to attempt to access an non-existent entry:
BEGIN{
LINT = "fatal";
thread = 0;
if (threads_start[thread]) {
print "if";
} else {
print "not if";
}
}
produces:
gawk: cmd. line:4: fatal: reference to uninitialized element `threads_start["0"]'
So, you don't really need to create an empty array in Awk, but if you want to do so, and answer your question, use split():
BEGIN{
LINT = "fatal";
thread = 0;
split("", threads_start);
if (thread in threads_start) {
print "if";
} else {
print "not if";
}
}
produces:
not if