OK, I've got this log file that has the date/time stamp in field one ($1) and I just wanted to extract the last ten minutes worth of data. Using the gawk functions mktime() and strftime() I wrote the following solution:
Getting the last ten minutes (600 seconds) of a log file:
awk -F, '{ tenago = strftime("%H:%M:%S", mktime(strftime("%Y %m %d %H %M %S", systime() - 600))); if ( substr($1,14) > tenago ) print $0 }' logfile.txt
Getting Hours:Minutes:Seconds timestamp for ten minutes ago was accomplished using:
tenago = strftime("%H:%M:%S", mktime(strftime("%Y %m %d %H %M %S", systime() - 600)))
It makes sense if you read it from the inside out. The systime() function, because it hasn't been supplied any parameters returns the current datetime minus (-) 600 seconds. It's formatted as a string using: "%Y %m %d %H %M %S" and passed to the mktime() function where it's converted to a string again... I think you may be able to simplify this - let me know if you can! :)
EDIT: Here we go, I knew there was a way to simplify the above command! Here's my latest version:
awk -F, '{ tenago = strftime("%H:%M:%S", systime() - 600); if ( substr($1,14) > tenago ) print $0 }'
Showing posts with label awk. Show all posts
Showing posts with label awk. Show all posts
Monday, 10 November 2008
Wednesday, 15 October 2008
awk script to find the maximum value of many returned rows
Below is a complete script that pulls out the maximum value for two specific criteria (disk name and time) - more than one row is returned for each so for the value in $3 we had to find the maximum.
for FILE in $( ls /var/test/perf/archive/measure/input/*$1*disk2.dat.gz )
do
DATFILE=$( echo ${FILE} | awk -F'/' '{ print substr($8,1,length($8)-3) }' )
echo ${DATFILE}
cp ${FILE} .
gunzip *.gz
DATE=$( echo ${DATFILE} | awk -F'-' '{ print $4"/"$3"/"$2 }' )
FILENAME=$( echo ${DATFILE} | awk -F'-' '{ print $1 }' )
awk -v D=${DATE} -F',' '{
if ( $2 ~ /D0201/ && $3 ~ /20:00:00/) {
max201[$3] = ( max201[$3] > $6 ? max201[$3] : $6 )
}
if ( $2 ~ /D0211/ && $3 ~ /20:00:00/) {
max211[$3] = ( max211[$3] > $6 ? max211[$3] : $6 )
}
} END {
for (i in max201) print D","i",D0201,"max201[i];
for (i in max211) print D","i",D0211,"max211[i];
}' ${DATFILE} >> ${FILENAME}-disk2.csv
rm ${DATFILE}
done
---
The clever bit (for me anyway) is this:
max211[$3] = ( max211[$3] > $6 ? max211[$3] : $6 )
The bit between the brackets is saying if the value in $6 is greater than what's already stored in max211[$3] (the value of $6 indexed by column $3) then pass back the new value of $6 - which then updates max211[$3] to the new value.
To put it another way; the bit above between the brackets could be written like this:
Or as my book tells me:
Awk provides a conditional operator that is found in the C programming language.
Its form is:
for FILE in $( ls /var/test/perf/archive/measure/input/*$1*disk2.dat.gz )
do
DATFILE=$( echo ${FILE} | awk -F'/' '{ print substr($8,1,length($8)-3) }' )
echo ${DATFILE}
cp ${FILE} .
gunzip *.gz
DATE=$( echo ${DATFILE} | awk -F'-' '{ print $4"/"$3"/"$2 }' )
FILENAME=$( echo ${DATFILE} | awk -F'-' '{ print $1 }' )
awk -v D=${DATE} -F',' '{
if ( $2 ~ /D0201/ && $3 ~ /20:00:00/) {
max201[$3] = ( max201[$3] > $6 ? max201[$3] : $6 )
}
if ( $2 ~ /D0211/ && $3 ~ /20:00:00/) {
max211[$3] = ( max211[$3] > $6 ? max211[$3] : $6 )
}
} END {
for (i in max201) print D","i",D0201,"max201[i];
for (i in max211) print D","i",D0211,"max211[i];
}' ${DATFILE} >> ${FILENAME}-disk2.csv
rm ${DATFILE}
done
---
The clever bit (for me anyway) is this:
max211[$3] = ( max211[$3] > $6 ? max211[$3] : $6 )
The bit between the brackets is saying if the value in $6 is greater than what's already stored in max211[$3] (the value of $6 indexed by column $3) then pass back the new value of $6 - which then updates max211[$3] to the new value.
To put it another way; the bit above between the brackets could be written like this:
if ( max211[$3] > $6 )
max211[$3] = max211[$3]
else
max211[$3] = $6
Or as my book tells me:
Awk provides a conditional operator that is found in the C programming language.
Its form is:
expr ? action1 : action2
Wednesday, 3 September 2008
awk: using the length() function
OK, take the following code snippit as an example:
$ echo this.is.test-for-awk | awk -F'.' '{ printf substr($3,6,length($3)-9)"\nLength of $3: "length($3)"\n" }'
Output:
for
Length of $3: 12
All the above awk command is doing is accepting the input of "this.is.test-for-awk" from an echo command and splitting it into it's component parts as described within the body of the awk command.
As you can see the input has two different types of field separators - which can be quite common. So I thought I'd start with the '.' separator and I've defined this by using the -F'.' flag.
I then wanted to split down what has now been defined as $3 (as the separator is '.'). This I have done using the substring and length functions that are built into awk. Strictly I didn't need to use the length function as I could have just put in the number '3' so this component of the command would've looked like this:
substr($3,6,3)
The above substr() command translates to: strip $3 (field 3) from digit number 6 to digit number 3. But I wanted to show how the length function could be used so this component of the command looked like this:
substr($3,6,length($3)-9)
Now, what the above is doing is splitting down $3 based starting from the 6th digit and then getting the length of the field and taking away 9. As you can see the length of $3 is 12 digits - including the separators. So by taking away 9 we're left with 3. The result is that we have the output 'for'.
The last part of the command proves that the length function is reading the input text correctly by showing us the length of field 3.
"\nLength of $3: "length($3)"\n"
Because I've used the printf function I can format the output to make it more readable by including \n to print new-lines.
$ echo this.is.test-for-awk | awk -F'.' '{ printf substr($3,6,length($3)-9)"\nLength of $3: "length($3)"\n" }'
Output:
for
Length of $3: 12
All the above awk command is doing is accepting the input of "this.is.test-for-awk" from an echo command and splitting it into it's component parts as described within the body of the awk command.
As you can see the input has two different types of field separators - which can be quite common. So I thought I'd start with the '.' separator and I've defined this by using the -F'.' flag.
I then wanted to split down what has now been defined as $3 (as the separator is '.'). This I have done using the substring and length functions that are built into awk. Strictly I didn't need to use the length function as I could have just put in the number '3' so this component of the command would've looked like this:
substr($3,6,3)
The above substr() command translates to: strip $3 (field 3) from digit number 6 to digit number 3. But I wanted to show how the length function could be used so this component of the command looked like this:
substr($3,6,length($3)-9)
Now, what the above is doing is splitting down $3 based starting from the 6th digit and then getting the length of the field and taking away 9. As you can see the length of $3 is 12 digits - including the separators. So by taking away 9 we're left with 3. The result is that we have the output 'for'.
The last part of the command proves that the length function is reading the input text correctly by showing us the length of field 3.
"\nLength of $3: "length($3)"\n"
Because I've used the printf function I can format the output to make it more readable by including \n to print new-lines.
Friday, 22 August 2008
Gnu Awk - gawk - Manual
This is a very useful resource - although it's specifically talking about gawk most of it will be applicable to other variations of awk:
http://www.gnu.org/software/gawk/manual/
http://www.gnu.org/software/gawk/manual/
Thursday, 21 August 2008
awk: terms and descriptions
I found this article by IBM that very clearly describes the different components of an awk program:
http://www.ibm.com/developerworks/library/l-awk1.html
http://www.ibm.com/developerworks/library/l-awk1.html
awk: more complex examples
EXAMPLES # is the comment character for awk. 'field' means 'column'
# Print first two fields in opposite order:
awk '{ print $2, $1 }' file
# Print lines longer than 72 characters:
awk 'length > 72' file
# Print length of string in 2nd column
awk '{print length($2)}' file
# Add up first column, print sum and average:
{ s += $1 }
END { print "sum is", s, " average is", s/NR }
# Print fields in reverse order:
awk '{ for (i = NF; i > 0; --i) print $i }' file
# Print the last line
{line = $0}
END {print line}
# Print the total number of lines that contain the word Pat
/Pat/ {nlines = nlines + 1}
END {print nlines}
# Print all lines between start/stop pairs:
awk '/start/, /stop/' file
# Print all lines whose first field is different from previous one:
awk '$1 != prev { print; prev = $1 }' file
# Print column 3 if column 1 > column 2:
awk '$1 > $2 {print $3}' file
# Print line if column 3 > column 2:
awk '$3 > $2' file
# Count number of lines where col 3 > col 1
awk '$3 > $1 {print i + "1"; i++}' file
# Print sequence number and then column 1 of file:
awk '{print NR, $1}' file
# Print every line after erasing the 2nd field
awk '{$2 = ""; print}' file
# Print hi 28 times
yes | head -28 | awk '{ print "hi" }'
# Print hi.0010 to hi.0099 (NOTE IRAF USERS!)
yes | head -90 | awk '{printf("hi00%2.0f \n", NR+9)}'
# Print out 4 random numbers between 0 and 1
yes | head -4 | awk '{print rand()}'
# Print out 40 random integers modulo 5
yes | head -40 | awk '{print int(100*rand()) % 5}'
# Replace every field by its absolute value
{ for (i = 1; i <= NF; i=i+1) if ($i < i =" -$i" 2="="" i="875;i">833;i--){
printf "lprm -Plw %d\n", i
} exit
}
Formatted printouts are of the form printf( "format\n", value1, value2, ... valueN)
e.g. printf("howdy %-8s What it is bro. %.2f\n", $1, $2*$3)
%s = string
%-8s = 8 character string left justified
%.2f = number with 2 places after .
%6.2f = field 6 chars with 2 chars after .
\n is newline
\t is a tab
# Print frequency histogram of column of numbers
$2 <= 0.1 {na=na+1} ($2 > 0.1) && ($2 <= 0.2) {nb = nb+1} ($2 > 0.2) && ($2 <= 0.3) {nc = nc+1} ($2 > 0.3) && ($2 <= 0.4) {nd = nd+1} ($2 > 0.4) && ($2 <= 0.5) {ne = ne+1} ($2 > 0.5) && ($2 <= 0.6) {nf = nf+1} ($2 > 0.6) && ($2 <= 0.7) {ng = ng+1} ($2 > 0.7) && ($2 <= 0.8) {nh = nh+1} ($2 > 0.8) && ($2 <= 0.9) {ni = ni+1} ($2 > 0.9) {nj = nj+1}
END {print na, nb, nc, nd, ne, nf, ng, nh, ni, nj, NR}
# Find maximum and minimum values present in column 1
NR == 1 {m=$1 ; p=$1}
$1 >= m {m = $1}
$1 <= p {p = $1} END { print "Max = " m, " Min = " p } # Example of defining variables, multiple commands on one line NR == 1 {prev=$4; preva = $1; prevb = $2; n=0; sum=0} $4 != prev {print preva, prevb, prev, sum/n; n=0; sum=0; prev = $4; preva = $1; prevb = $2} $4 == prev {n++; sum=sum+$5/$6} END {print preva, prevb, prev, sum/n} # Example of defining and using a function, inserting values into an array # and doing integer arithmetic mod(n). This script finds the number of days # elapsed since Jan 1, 1901. (from http://www.netlib.org/research/awkbookcode/ch3) function daynum(y, m, d, days, i, n) { # 1 == Jan 1, 1901 split("31 28 31 30 31 30 31 31 30 31 30 31", days) # 365 days a year, plus one for each leap year n = (y-1901) * 365 + int((y-1901)/4) if (y % 4 == 0) # leap year from 1901 to 2099 days[2]++ for (i = 1; i < m; i++) n += days[i] return n + d } { print daynum($1, $2, $3) } # Example of using substrings # substr($2,9,7) picks out characters 9 thru 15 of column 2 {print "imarith", substr($2,1,7) " - " $3, "out."substr($2,5,3)} {print "imarith", substr($2,9,7) " - " $3, "out."substr($2,13,3)} {print "imarith", substr($2,17,7) " - " $3, "out."substr($2,21,3)} {print "imarith", substr($2,25,7) " - " $3, "out."substr($2,29,3)}
# Print first two fields in opposite order:
awk '{ print $2, $1 }' file
# Print lines longer than 72 characters:
awk 'length > 72' file
# Print length of string in 2nd column
awk '{print length($2)}' file
# Add up first column, print sum and average:
{ s += $1 }
END { print "sum is", s, " average is", s/NR }
# Print fields in reverse order:
awk '{ for (i = NF; i > 0; --i) print $i }' file
# Print the last line
{line = $0}
END {print line}
# Print the total number of lines that contain the word Pat
/Pat/ {nlines = nlines + 1}
END {print nlines}
# Print all lines between start/stop pairs:
awk '/start/, /stop/' file
# Print all lines whose first field is different from previous one:
awk '$1 != prev { print; prev = $1 }' file
# Print column 3 if column 1 > column 2:
awk '$1 > $2 {print $3}' file
# Print line if column 3 > column 2:
awk '$3 > $2' file
# Count number of lines where col 3 > col 1
awk '$3 > $1 {print i + "1"; i++}' file
# Print sequence number and then column 1 of file:
awk '{print NR, $1}' file
# Print every line after erasing the 2nd field
awk '{$2 = ""; print}' file
# Print hi 28 times
yes | head -28 | awk '{ print "hi" }'
# Print hi.0010 to hi.0099 (NOTE IRAF USERS!)
yes | head -90 | awk '{printf("hi00%2.0f \n", NR+9)}'
# Print out 4 random numbers between 0 and 1
yes | head -4 | awk '{print rand()}'
# Print out 40 random integers modulo 5
yes | head -40 | awk '{print int(100*rand()) % 5}'
# Replace every field by its absolute value
{ for (i = 1; i <= NF; i=i+1) if ($i < i =" -$i" 2="="" i="875;i">833;i--){
printf "lprm -Plw %d\n", i
} exit
}
Formatted printouts are of the form printf( "format\n", value1, value2, ... valueN)
e.g. printf("howdy %-8s What it is bro. %.2f\n", $1, $2*$3)
%s = string
%-8s = 8 character string left justified
%.2f = number with 2 places after .
%6.2f = field 6 chars with 2 chars after .
\n is newline
\t is a tab
# Print frequency histogram of column of numbers
$2 <= 0.1 {na=na+1} ($2 > 0.1) && ($2 <= 0.2) {nb = nb+1} ($2 > 0.2) && ($2 <= 0.3) {nc = nc+1} ($2 > 0.3) && ($2 <= 0.4) {nd = nd+1} ($2 > 0.4) && ($2 <= 0.5) {ne = ne+1} ($2 > 0.5) && ($2 <= 0.6) {nf = nf+1} ($2 > 0.6) && ($2 <= 0.7) {ng = ng+1} ($2 > 0.7) && ($2 <= 0.8) {nh = nh+1} ($2 > 0.8) && ($2 <= 0.9) {ni = ni+1} ($2 > 0.9) {nj = nj+1}
END {print na, nb, nc, nd, ne, nf, ng, nh, ni, nj, NR}
# Find maximum and minimum values present in column 1
NR == 1 {m=$1 ; p=$1}
$1 >= m {m = $1}
$1 <= p {p = $1} END { print "Max = " m, " Min = " p } # Example of defining variables, multiple commands on one line NR == 1 {prev=$4; preva = $1; prevb = $2; n=0; sum=0} $4 != prev {print preva, prevb, prev, sum/n; n=0; sum=0; prev = $4; preva = $1; prevb = $2} $4 == prev {n++; sum=sum+$5/$6} END {print preva, prevb, prev, sum/n} # Example of defining and using a function, inserting values into an array # and doing integer arithmetic mod(n). This script finds the number of days # elapsed since Jan 1, 1901. (from http://www.netlib.org/research/awkbookcode/ch3) function daynum(y, m, d, days, i, n) { # 1 == Jan 1, 1901 split("31 28 31 30 31 30 31 31 30 31 30 31", days) # 365 days a year, plus one for each leap year n = (y-1901) * 365 + int((y-1901)/4) if (y % 4 == 0) # leap year from 1901 to 2099 days[2]++ for (i = 1; i < m; i++) n += days[i] return n + d } { print daynum($1, $2, $3) } # Example of using substrings # substr($2,9,7) picks out characters 9 thru 15 of column 2 {print "imarith", substr($2,1,7) " - " $3, "out."substr($2,5,3)} {print "imarith", substr($2,9,7) " - " $3, "out."substr($2,13,3)} {print "imarith", substr($2,17,7) " - " $3, "out."substr($2,21,3)} {print "imarith", substr($2,25,7) " - " $3, "out."substr($2,29,3)}
awk: more simple examples
First, suppose you have a file called 'file1' that has 2 columns of numbers, and you want to make a new file called 'file2' that has columns 1 and 2 as before, but also adds a third column which is the ratio of the numbers in columns 1 and 2. Suppose you want the new 3-column file (file2) to contain only those lines with column 1 smaller than column 2. Either of the following two commands does what you want:
awk '$1 < $2 {print $0, $1/$2}' file1 > file2
-- or --
cat file1 | awk '$1 < $2 {print $0, $1/$2}' > file2
Let's look at the second one. You all know that 'cat file1' prints the contents of file1 to your screen. The | (called a pipe) directs the output of 'cat file1', which normally goes to your screen, to the command awk. Awk considers the input from 'cat file1' one line at a time, and tries to match the 'pattern'. The pattern is whatever is between the first ' and the {, in this case the pattern is $1 < $2. If the pattern is false, awk goes on to the next line. If the pattern is true, awk does whatever is in the {}. In this case we have asked awk to check if the first column is less than the second. If there is no pattern, awk assumes the pattern is true, and goes onto the action contained in the {}.
What is the action? Almost always it is a print statement of some sort. In this case we want awk to print the entire line, i.e. $0, and then print the ratio of columns 1 and 2, i.e. $1/$2. We close the action with a }, and close the awk command with a '. Finally, to store the final 3-column output into file2 (otherwise it prints to the screen), we add a '> file2'.
As a second example, suppose you have several thousand files you want to move into a new directory and rename by appending a .dat to the filenames. You could do this one by one (several hours), or use vi to make a decent command file to do it (several minutes), or use awk (several seconds). Suppose the files are named junk* (* is wildcard for any sequence of characters), and need to be moved to ../iraf and have a '.dat' appended to the name. To do this type
ls junk* | awk '{print "mv "$0" ../iraf/"$0".dat"}' | csh
ls junk* lists the filenames, and this output is piped into awk instead of going to your screen. There is no pattern (nothing between the ' and the {), so awk proceeds to print something for each line. For example, if the first two lines from 'ls junk*' produced junk1 and junk2, respectively, then awk would print:
mv junk1 ../iraf/junk1.dat
mv junk2 ../iraf/junk2.dat
At this point the mv commands are simply printed to the screen. To execute the command we take the output of awk and pipe it back into the operating system (the C-shell). Hence, to finish the statement we add a ' | csh'.
More complex awk scripts need to be run from a file. The syntax for such cases is:
cat file1 | awk -f a.awk > file2
where file1 is the input file, file2 is the output file, and a.awk is a file containing awk commands. Examples below that contain more than one line of awk need to be run from files.
Some useful awk variables defined for you are NF (number of columns), NR (the current line that awk is working on), END (true if awk reaches the EOF), BEGIN (true before awk reads anything), and length (number of characters in a line or a string). There is also looping capability, a search (/) command, a substring command (extremely useful), and formatted printing available. There are logical variables || (or) and && (and) that can be used in 'pattern'. You can define and manipulate your own user defined variables. Examples are outlined below. The only bug I know of is that Sun's version of awk won't do trig functions, though it does do logs. There is something called gawk (a Gnu product), which does a few more things than Sun's awk, but they are basically the same. Note the use of the 'yes' command below. Coupled with 'head' and 'awk' you save an hour of typing if you have a lot of files to analyze or rename.
awk '$1 < $2 {print $0, $1/$2}' file1 > file2
-- or --
cat file1 | awk '$1 < $2 {print $0, $1/$2}' > file2
Let's look at the second one. You all know that 'cat file1' prints the contents of file1 to your screen. The | (called a pipe) directs the output of 'cat file1', which normally goes to your screen, to the command awk. Awk considers the input from 'cat file1' one line at a time, and tries to match the 'pattern'. The pattern is whatever is between the first ' and the {, in this case the pattern is $1 < $2. If the pattern is false, awk goes on to the next line. If the pattern is true, awk does whatever is in the {}. In this case we have asked awk to check if the first column is less than the second. If there is no pattern, awk assumes the pattern is true, and goes onto the action contained in the {}.
What is the action? Almost always it is a print statement of some sort. In this case we want awk to print the entire line, i.e. $0, and then print the ratio of columns 1 and 2, i.e. $1/$2. We close the action with a }, and close the awk command with a '. Finally, to store the final 3-column output into file2 (otherwise it prints to the screen), we add a '> file2'.
As a second example, suppose you have several thousand files you want to move into a new directory and rename by appending a .dat to the filenames. You could do this one by one (several hours), or use vi to make a decent command file to do it (several minutes), or use awk (several seconds). Suppose the files are named junk* (* is wildcard for any sequence of characters), and need to be moved to ../iraf and have a '.dat' appended to the name. To do this type
ls junk* | awk '{print "mv "$0" ../iraf/"$0".dat"}' | csh
ls junk* lists the filenames, and this output is piped into awk instead of going to your screen. There is no pattern (nothing between the ' and the {), so awk proceeds to print something for each line. For example, if the first two lines from 'ls junk*' produced junk1 and junk2, respectively, then awk would print:
mv junk1 ../iraf/junk1.dat
mv junk2 ../iraf/junk2.dat
At this point the mv commands are simply printed to the screen. To execute the command we take the output of awk and pipe it back into the operating system (the C-shell). Hence, to finish the statement we add a ' | csh'.
More complex awk scripts need to be run from a file. The syntax for such cases is:
cat file1 | awk -f a.awk > file2
where file1 is the input file, file2 is the output file, and a.awk is a file containing awk commands. Examples below that contain more than one line of awk need to be run from files.
Some useful awk variables defined for you are NF (number of columns), NR (the current line that awk is working on), END (true if awk reaches the EOF), BEGIN (true before awk reads anything), and length (number of characters in a line or a string). There is also looping capability, a search (/) command, a substring command (extremely useful), and formatted printing available. There are logical variables || (or) and && (and) that can be used in 'pattern'. You can define and manipulate your own user defined variables. Examples are outlined below. The only bug I know of is that Sun's version of awk won't do trig functions, though it does do logs. There is something called gawk (a Gnu product), which does a few more things than Sun's awk, but they are basically the same. Note the use of the 'yes' command below. Coupled with 'head' and 'awk' you save an hour of typing if you have a lot of files to analyze or rename.
awk: appending fields from one row to the preceding row
How to append fields from one row on to the end of the preceding row - the awk one isn't completely tested yet - this gives the output the other way round...
Try...
paste -d" " - - <> outfile
Or...
awk -v RS="" '{$1=$1};1' infile > outfile
If anyone know's how to improve this please let me know! ...Also I'm not sure why the '-v' variable declaration is required?
Try...
paste -d" " - - <> outfile
Or...
awk -v RS="" '{$1=$1};1' infile > outfile
If anyone know's how to improve this please let me know! ...Also I'm not sure why the '-v' variable declaration is required?
Tuesday, 19 August 2008
awk: numerical addition and grouping
The following script performs a count of the numerical data in a column ($3) and then groups that data by $1.
awk -F'#' 'BEGIN {}
{
sum[$1] += $3
} END {
for ( i in sum ) print i" : "sum[i]
}' $1
Notice how I'm making the value of the array sum[$1] the addition of the numerical values found at field three ($3):
sum[$1] += $3
Essentially evertime awk comes across a $1 value it adds the value of $3 to the array index of $1.
awk -F'#' 'BEGIN {}
{
sum[$1] += $3
} END {
for ( i in sum ) print i" : "sum[i]
}' $1
Notice how I'm making the value of the array sum[$1] the addition of the numerical values found at field three ($3):
sum[$1] += $3
Essentially evertime awk comes across a $1 value it adds the value of $3 to the array index of $1.
awk: printing from a specified field onwards
OK, this is way to complicated when a simple substr() on $0 will suffice but I like it!
awk '{for(i=1;i<4;i++)$i="";sub(/^ */,"");print}' error.txt | sort -u
Notice that it's piped into a sort because awk doesn't have it's own sort command.
All this command is doing is printing everything after field four.
Below is a modification of the above command that prints only the unique lines (based on all fields after field 4):
awk '{for(i=1;i<4;i++)$i=""; sub(/^ */,""); err[$0] = $0 } END { for ( u in err ) print err[u] }'
Notice that $0 is the modified $0 without fields one thru 4.
awk '{for(i=1;i<4;i++)$i="";sub(/^ */,"");print}' error.txt | sort -u
Notice that it's piped into a sort because awk doesn't have it's own sort command.
All this command is doing is printing everything after field four.
Below is a modification of the above command that prints only the unique lines (based on all fields after field 4):
awk '{for(i=1;i<4;i++)$i=""; sub(/^ */,""); err[$0] = $0 } END { for ( u in err ) print err[u] }'
Notice that $0 is the modified $0 without fields one thru 4.
awk: working out thresholds
The following code was written to work out threshold breaches - the input is standard sar data.
The interesting part is the alert.awk script at the bottom.
The shell script wrapper defines the following variables that are passed to the awk code:
${i} - is the system name
amb - is the amber alert threshold value
count - is the amount of times this threshold occurs
count=`grep ${i} alert_cpu.conf|awk -F, '{ print $4 }'`
amb=`grep ${i} alert_cpu.conf|awk -F, '{ print $2 }'`
awk -v val=${amb} -v count=${count} -f alert.awk cpu2.dat >>detailed_alert_report_${DAY}.txt
alert.awk script:
$(NF)>val {c++; o=o $0 ORS; next}
c>count {printf "%s",o; C++}
{c=0; o=""}
END {if (c>count) {printf "%s",o; C++} print C+0}
The interesting part is the alert.awk script at the bottom.
The shell script wrapper defines the following variables that are passed to the awk code:
${i} - is the system name
amb - is the amber alert threshold value
count - is the amount of times this threshold occurs
count=`grep ${i} alert_cpu.conf|awk -F, '{ print $4 }'`
amb=`grep ${i} alert_cpu.conf|awk -F, '{ print $2 }'`
awk -v val=${amb} -v count=${count} -f alert.awk cpu2.dat >>detailed_alert_report_${DAY}.txt
alert.awk script:
$(NF)>val {c++; o=o $0 ORS; next}
c>count {printf "%s",o; C++}
{c=0; o=""}
END {if (c>count) {printf "%s",o; C++} print C+0}
awk: putting blank lines between non-alike lines
I had some output that was really difficult to read and so I wanted to split the output into blocks that were similar - where they had the same $1 value. This fab little awk command puts a blank line between each block (multiple lines) where $1 changes.
(don't ask me how it works tho because I got it off a really nice chap at www.unix.com)
awk 'x[$1]++||$0=NR==1?$0:RS $0' test2
If anyone can explain it to me please feel free! :)
(don't ask me how it works tho because I got it off a really nice chap at www.unix.com)
awk 'x[$1]++||$0=NR==1?$0:RS $0' test2
If anyone can explain it to me please feel free! :)
awk: getting data based on previous lines
Here's a little awk script that I wrote to collect data from a field where it's heading is in the line above (both are at $4 - space separated). The input file looks like this:
ARTMGA01_usage_07Mar07-1500:# CPU SUMMARY
ARTMGA01_usage_07Mar07-1500-# USER SYS IDLE WAIT INTR SYSC CS RUNQ AVG5 AVG30 AVG60 FORK VFORK
ARTMGA01_usage_07Mar07-1500- 4 6 90 0 1002 7214 9093 0 0.21 0.18 0.17 2.73 0.59
ARTMGA01_usage_07Mar07-1500:# CPU SUMMARY
ARTMGA01_usage_07Mar07-1500-# USER SYS IDLE WAIT INTR SYSC CS RUNQ AVG5 AVG30 AVG60 FORK VFORK
ARTMGA01_usage_07Mar07-1500- 4 9 87 0 905 7552 8530 1 0.32 0.21 0.18 2.20 0.40
ARTMGA01_usage_07Mar07-2100:# CPU SUMMARY
ARTMGA01_usage_07Mar07-2100-# USER SYS IDLE WAIT INTR SYSC CS RUNQ AVG5 AVG30 AVG60 FORK VFORK
ARTMGA01_usage_07Mar07-2100- 4 5 90 0 1052 7777 9492 0 0.19 0.18 0.18 2.65 0.59
Here's the actual code:
BEGIN {}
{
if ($4 ~ /IDLE/) {
getline
idleval = idleval + $4
count++
}
}
END {
print "Total: " idleval
print "Count: " count
print "Average: " idleval / count
}
There's nothing new in this code that we haven't already covered but it's a good 'simple' example of how awk's getline function can be used. We essentially do a search for 'IDLE' then skip to the next line with getline and collect the value at $4 (field four). The 'count' value is incremented for every line that matches 'IDLE' which is how we can then work out the average:
print "Average: " idleval / count
('idleval' divided by 'count').
ARTMGA01_usage_07Mar07-1500:# CPU SUMMARY
ARTMGA01_usage_07Mar07-1500-# USER SYS IDLE WAIT INTR SYSC CS RUNQ AVG5 AVG30 AVG60 FORK VFORK
ARTMGA01_usage_07Mar07-1500- 4 6 90 0 1002 7214 9093 0 0.21 0.18 0.17 2.73 0.59
ARTMGA01_usage_07Mar07-1500:# CPU SUMMARY
ARTMGA01_usage_07Mar07-1500-# USER SYS IDLE WAIT INTR SYSC CS RUNQ AVG5 AVG30 AVG60 FORK VFORK
ARTMGA01_usage_07Mar07-1500- 4 9 87 0 905 7552 8530 1 0.32 0.21 0.18 2.20 0.40
ARTMGA01_usage_07Mar07-2100:# CPU SUMMARY
ARTMGA01_usage_07Mar07-2100-# USER SYS IDLE WAIT INTR SYSC CS RUNQ AVG5 AVG30 AVG60 FORK VFORK
ARTMGA01_usage_07Mar07-2100- 4 5 90 0 1052 7777 9492 0 0.19 0.18 0.18 2.65 0.59
Here's the actual code:
BEGIN {}
{
if ($4 ~ /IDLE/) {
getline
idleval = idleval + $4
count++
}
}
END {
print "Total: " idleval
print "Count: " count
print "Average: " idleval / count
}
There's nothing new in this code that we haven't already covered but it's a good 'simple' example of how awk's getline function can be used. We essentially do a search for 'IDLE' then skip to the next line with getline and collect the value at $4 (field four). The 'count' value is incremented for every line that matches 'IDLE' which is how we can then work out the average:
print "Average: " idleval / count
('idleval' divided by 'count').
awk: pipes and file stuff
Here I'm using the output of a standard *nix 'ls' command and piping it into various awk commands to give me the count of specific fields - file size etc.. Notice also that I'm using the printf command to help better display the output.
How to get the total size of all the files in your current directory and directories within
your current directory (recursive):
ls -lrtR | awk '{sum += $5;} END {print sum;}'
As an extension to the above so that you can format the output looks like this (notice that we're actually defining the BEGIN statement here - you don't actually have to do this but it makes the code easier to read):
ls -lrtR alg | awk 'BEGIN { printf "Directory\t : Size\n" } {sum += $5;} END {printf "alg\t\t : " sum"\n";}'
Which provides output that looks like this:
Directory : Size
alg : 120467
And as another extension to this; here's how you script it so that it can take more than one
directory:
printf "Directory\t : Size\n"
for arg
do
$ ls -lrtR $arg | awk '{sum += $5;} END {printf "'"$arg"'\t\t : " sum"\n";}'
done
$ awkdir alg alg1 fred
Directory : Size
alg : 120467
alg1 : 123209
fred : 123209
How to get the total size of all the files in your current directory and directories within
your current directory (recursive):
ls -lrtR | awk '{sum += $5;} END {print sum;}'
As an extension to the above so that you can format the output looks like this (notice that we're actually defining the BEGIN statement here - you don't actually have to do this but it makes the code easier to read):
ls -lrtR alg | awk 'BEGIN { printf "Directory\t : Size\n" } {sum += $5;} END {printf "alg\t\t : " sum"\n";}'
Which provides output that looks like this:
Directory : Size
alg : 120467
And as another extension to this; here's how you script it so that it can take more than one
directory:
printf "Directory\t : Size\n"
for arg
do
$ ls -lrtR $arg | awk '{sum += $5;} END {printf "'"$arg"'\t\t : " sum"\n";}'
done
$ awkdir alg alg1 fred
Directory : Size
alg : 120467
alg1 : 123209
fred : 123209
awk: finding the count of a field
OK, so I had this file that had different types of events in it and I wanted to find the count of how many occurences there were for each event type. The file was comma separated and the events were at $1 (the first field) and looked like this:
TRAF:5
TRAF:8
TRAF:3
Here's the awk command that got the result I was after:
awk -F, '{ te[$1]++ } END { for ( i in te ) print i" : " te[i] }' traf.test
Here's what it's doing:
The file delimiter flag '-F,' we're already familiar with. This tells awk that the file is comma separated.
Now, in the next bit we're introducing awk arrays for the first time: "te[$1]++"
We're creating an array called 'te', you can call this anything you like. We're creating an index in our array based on the contents of $1 (the first field) which is our traffic event type. The double-plus signs are saying that the value of te[$1] is to be incremented. So, what happens is that an array index is created for every unique value found in $1. That means that we've captured all the different possibilities of $1 with out doing much work at all. When awk finds another example of that same index value ($1) it increments the value of that array component.
Once we've gone through the whole file we get to the END section of the code. Here we're seeing a for loop for the first time:
for (i in te) print i" : " te[i]
This loop iterates through the array and prints out the index of the array (i) and then the value of that component. We end up with a print out of each unique value found at $1 and then a count of the occurences of that value.
TRAF:5
TRAF:8
TRAF:3
Here's the awk command that got the result I was after:
awk -F, '{ te[$1]++ } END { for ( i in te ) print i" : " te[i] }' traf.test
Here's what it's doing:
The file delimiter flag '-F,' we're already familiar with. This tells awk that the file is comma separated.
Now, in the next bit we're introducing awk arrays for the first time: "te[$1]++"
We're creating an array called 'te', you can call this anything you like. We're creating an index in our array based on the contents of $1 (the first field) which is our traffic event type. The double-plus signs are saying that the value of te[$1] is to be incremented. So, what happens is that an array index is created for every unique value found in $1. That means that we've captured all the different possibilities of $1 with out doing much work at all. When awk finds another example of that same index value ($1) it increments the value of that array component.
Once we've gone through the whole file we get to the END section of the code. Here we're seeing a for loop for the first time:
for (i in te) print i" : " te[i]
This loop iterates through the array and prints out the index of the array (i) and then the value of that component. We end up with a print out of each unique value found at $1 and then a count of the occurences of that value.
Monday, 18 August 2008
awk: find a pattern then return the previous line
Here's an awk script (it's a bit long to call it a command) that looks for a pattern and then returns the previous line. I attempted this when someone told me it could'nt be done with awk - I just did it to prove a point and spent way too much time figuring it out!
Command line:
awk -v N=PATTERN -f getprevline1 testinput.dat
Contents of getprevline1:
BEGIN {
(getline line1 <= ARGV[1])
}
{
if ((getline line < ARGV[1]) && $0 ~ /N/ ) {
print line
}
}
You can see that we're using the -v flag again to define a variable called N with the value 'PATTERN'. We're also using the -f (lowercase) flag for the first time. This flag calls the awk command file getprevline1.
We're also bombarded with the getline awk command which is something that should'nt be dabbled with lightly. In one of the awk books I have it essentially says don't use getline until you've mastered everything else!
Command line:
awk -v N=PATTERN -f getprevline1 testinput.dat
Contents of getprevline1:
BEGIN {
(getline line1 <= ARGV[1])
}
{
if ((getline line < ARGV[1]) && $0 ~ /N/ ) {
print line
}
}
You can see that we're using the -v flag again to define a variable called N with the value 'PATTERN'. We're also using the -f (lowercase) flag for the first time. This flag calls the awk command file getprevline1.
We're also bombarded with the getline awk command which is something that should'nt be dabbled with lightly. In one of the awk books I have it essentially says don't use getline until you've mastered everything else!
an introduction to awk
awk is a great *nix command line tool for extracting information from files - it's also so much more than that; it's a programming language in it's own right and if you know it well it's a great weapon to have in your armory.
Here I'll be explaining the very basics of the language so that you can build up a good solid understanding of how to use it. I'm no expert so what's displayed herein may not be the best method to reach each desired result and because of that I invite anyone to correct me.
I hope that all the code I show is equally useful to those that use nawk, gawk etc.
Here I'll be explaining the very basics of the language so that you can build up a good solid understanding of how to use it. I'm no expert so what's displayed herein may not be the best method to reach each desired result and because of that I invite anyone to correct me.
I hope that all the code I show is equally useful to those that use nawk, gawk etc.
Labels:
awk,
command line,
introduction,
linux,
tools,
unix
Subscribe to:
Posts (Atom)