21

I just want to implement if condition in awk. I created one file name : "simple_if" as below.

BEGIN{
num=$1;
if (num%2==0)
printf "%d is Even number.\n",num;
else printf "%d is odd Number.\n",num
}

Then i executed the program by passing 10 as argument for $1 as below

awk -f simple_if 10

But it doesn't take input and instead displays 0. Output:

0 is Even number.

How to get value from user in awk?

Dip
  • 680
  • You've confused Awk command line arguments (which must be read from the ARGV array) with Bash / Bourne shell positional parameters ($1, $2, and so on). Awk uses $1, $2, and so on to refer to the individual "fields" of an input line, after splitting on whitespace (by default), as best explained in mosvy's answer below. These are like the columns (A, B, C, and so on) in a spreadsheet program, except numbers instead of letters. – Kevin E Feb 12 '20 at 08:08

5 Answers5

30

Arguments given at the end of the command line to awk are generally taken as filenames that the awk script will read from. To set a variable on the command line, use -v variable=value, e.g.

awk -v num=10 -f script.awk

This would enable you to use num as a variable in your script. The initial value of the variable will be 10 in the above example.

You may also read environment variables using ENVIRON["variable"] in your script (for some environment variable named variable), or look at the command line arguments with ARGV[n] where n is some positive integer.


With $1 in awk, you would refer to the value of the first field in the current record, but since you are using it in a BEGIN block, no data has yet been read from any file.

The number in your code is being interpreted as zero since it's an empty variable used in an arithmetic context.

Kusalananda
  • 333,661
18

$1 is not the first command line argument, but the first field after the line was split with FS (and it will be the empty string in BEGIN, since no line was split yet).

Command line arguments are in the array ARGV:

$ awk 'BEGIN { for(i = 1; i < ARGC; i++) print ARGV[i] }' 1st 2nd 3rd
1st
2nd
3rd

ARGV[0] is always the name of the interpreter (awk or gawk, etc).

In order to let awk ignore a command line argument and not try to open it later as a file you should delete it or set it to the empty string: eg. ARGV[1]="".

As a side note, any argument of the form var=value will also be interpreted as a variable assignment by awk, and will be eval'ed after the file arguments that precede it have been processed:

$ echo yes > file
$ awk '{ gsub(/e/, var); print }' var=1 file var=2 file var=3 file
y1s
y2s
y3s

To use an actual filename of the form key=val with awk, you should pass it as a relative or absolute path eg. awk '{...}' ./key=val.

  • "In order to let awk ignore a command line argument and not try to open it later as a file you should delete it or set it to the empty string: eg. ARGV[1]=""." what exactly is the syntax for doing this? I tried for 5 minutes but it just ended up deleting the actual command line arg where I wanted to use it. – ijoseph Nov 20 '20 at 00:25
  • @ijoseph I'm not sure I understand you, but you can save it in another variable before that ;-) argv1=ARGV[1]; ARGV[1]="" –  Nov 21 '20 at 01:36
  • Ah. Thanks. In that case, one might as just well use the key-value syntax to begin with, rather than rewiring a position arg into a named one. Which is, I guess, why the above answer that does this is more highly upvoted. – ijoseph Nov 21 '20 at 02:07
  • @ijoseph I don't understand what you want to say by "rewiring a position arg into a named one". As mentioned in my answer, the diff between awk -v key=val file1 file2 and awk file1 key=val file2 is that in the latter case key will be set after file1 has been processed. –  Dec 06 '20 at 05:35
2

I would like to call out a key point mentioned in user313992's answer.

In order to let awk ignore a command line argument and not try to open it later as a file you should delete it or set it to the empty string: eg. ARGV[1]="".

Per POSIX awk's document, the argument can only be interpreted in one of two forms:

  1. a file to be read in
  2. the assignment form: var=val

So, if specified arguments that's not an existing file, and not in the assignment form, only upon reading that argument, awk will fail:

BEGIN {
 # this is OK:
 for (i=1; i<ARGC; i++) {
  print "+++ ", ARGV[i]
}

action blocks will fail, because file reading has started

{ print $1 }

Therefore, it is OK to specify arguments that aren't existing as files, however, one must remember to delete them from ARGV before the action blocks.

Example -

#!/bin/awk -f
#
# Bin count using thresholds given at argv. E.g.
# 
#   ./bin_count 0.1 0.01 0.001 0.0001 <./data | sort
#   < 0.000100: 3
#   < 0.001000: 12
#   < 0.010000: 56
#   < 0.100000: 100

BEGIN { for (i=1; i<ARGC; i++) counts[ARGV[i]] = 0 delete ARGV # <<<<< important } { $1 = $1 < 0 ? -$1 : $1 for (bin in counts) { if ($1 < bin) { counts[bin]++; } } } END { for (bin in counts) { printf("< %f: %d\n", bin, counts[bin]) } }

KFL
  • 269
  • 1
    More concise than my answer and bonus points for going straight to the POSIX standard! Wrapping the awk program in a string feels unnecessary, though. Are you doing that because shebang lines on some platforms can't contain more than one argument (e.g., #!/usr/bin/env awk -f would work on macOS, but not Linux)? – Kevin E Dec 16 '22 at 21:22
  • 1
    @TheDudeAbides thanks for the feedback. I updated it. For POSIX compliant, we can't use #!/usr/bin/env awk -f (see here) – KFL Dec 16 '22 at 21:41
1

As has been pointed out already, no input is being read in the BEGIN section. You could make your code execute as expected, if you check for the first input line and supply the number on stdin:

echo 10 | awk 'NR==1{
num=$1;
if (num%2==0)
printf "%d is Even number.\n",num;
else printf "%d is odd Number.\n",num
}'
10 is Even number.
RudiC
  • 8,969
  • Not sure what you mean by "no input is being read in the BEGIN section". The arguments on the command line are read, saved in the ARGV array, and available in the BEGIN section. The only error in the OP's try was to confuse shell positional parameters (accessed with $1 and similar) with awk command line arguments (accessed as ARGV[1] and similar) –  Jun 02 '20 at 14:13
0

Regular Awk has no problem processing command line arguments as any other C-like program would, without resorting to any GNU gawk-specific behavior, pipes or redirection (<), or the -v (variable assignment) option.

The handling of input arguments, ARGC (the argument count, an integer), and ARGV (the argument vector, another word for "list") are all covered in great detail in the manual.

Mosvy did a great job explaining the background, and summarizing what needs to be done to parse ARGV. Here is your original objective, implemented as a standalone shell script, tested on both macOS and GNU/Linux.

simple_if.awk

#!/usr/bin/awk -f
##
##  simple_if - tell user if a number given as an argument is even or odd
##
##  Example:    ./simple_if.awk 11
##
BEGIN {
    num = ARGV[1];

    # if you expect to arguments AND read from one or more input files, you need
    # to remove the arguments from ARGV so Awk doesn't attempt to open them as
    # files (causing an error)
    #ARGV[1] = "";

    if (num % 2 == 0) {
        printf "%d is an even number.\n", num;
    } else {
        printf "%d is an odd number.\n", num;
    }
}

Make an Awk script like this executable with chmod a+x scriptname.awk, put it in your $PATH, and it can run just as any other Bash, Python, Perl script, C program, whatever.

If awk exists on some other place on your system, update the #! line appropriately; it may not be possible to use /usr/bin/env because awk must have the -f option to run your script, and… it's complicated.

The .awk extension isn't necessary at all, but it may help your editor to enable proper syntax highlighting. Leave it off and no one even needs to know it's an Awk script.


Here's a more complete example that does something useful, and has reasonable error-handling:

simple_stats.awk

#!/usr/bin/awk -f
##
##  simple_stats - do simple 1-variable statistics (min/max/sum/average) on
##                 the first column of its input file(s)
##
##  examples:      ./simple_stats min numbers.txt
##                 ./simple_stats all numbers.txt    # all stats
##                 ./simple_stats sum <(du MyFiles)  # Bash proc. substitution
##
##                 # specify '-' as the filename when reading from stdin
##                 seq 1 100 | ./simple_stats avg -
##
BEGIN {
    # expect stats operation as the first argument
    op = ARGV[1]

    # unset this array index so Awk doesn't try opening it as a file later
    # ref: https://www.gnu.org/software/gawk/manual/html_node/ARGC-and-ARGV.html
    ARGV[1] = ""  

    # if you wanted to process multiple command line arguments here, you could
    # loop over ARGV, using something like
    # for (i=0; i<ARGC; i++) { if (ARGV[i] == "...") { ... } }

    if (op !~ /^(min|max|sum|avg|all)$/) {
        print "ERROR: Expected one of min/max/sum/avg/all." >"/dev/stderr"
        # 'exit' in BEGIN will always run the EXIT block, if one exists
        # see https://www.gnu.org/software/gawk/manual/html_node/Assert-Function.html
        _assert_exit = 1
        exit 1
    }

    # ordinarily Awk reads stdin without specifying; here, '-' seems necessary
    if (ARGV[2] == "") {
        print "ERROR: Need an input file (or '-' for stdin)." >"/dev/stderr"
        _assert_exit = 1
        exit 1
    }
}

# 'min' needs an initial value so take the first record
NR == 1 { min = $1 }

# for every input line (including the first)...
{
    sum += $1
    if ($1 > max) max = $1
    if ($1 < min) min = $1
}

END {
    if (_assert_exit) exit 1;  # if 'exit' was pending from BEGIN block

    if (op == "min" || op == "all")
        printf "The minimum is: %15d\n", min
    if (op == "max" || op == "all")
        printf "The maximum is: %15d\n", max
    if (op == "sum" || op == "all")
        printf "The sum is:     %15d\n", sum
    if (op == "avg" || op == "all")
        printf "The average is: %15.2f\n", sum/NR
}
Kevin E
  • 468
  • This is gawk specific (the manual you referenced is also gawk). Per POSIX awk, the arguments are either files to read from, or the assignment pairs. – KFL Dec 15 '22 at 06:57
  • @KFL that's a fair point about linking to GNU Awk documentation, but I find it does a good enough job pointing out the non-standard GNU extensions so as to serve as a general reference for the language. This was tested with mawk, macOS/BSD awk, and gawk --posix, and seems to work in each case, so I think it should work for any local implementations that support "new awk" features, regardless of OS. – Kevin E Dec 16 '22 at 21:09