{"id":22958,"date":"2020-09-29T17:07:49","date_gmt":"2020-09-29T15:07:49","guid":{"rendered":"http:\/\/cornay.co.za\/wp\/?p=22958"},"modified":"2020-09-29T17:07:49","modified_gmt":"2020-09-29T15:07:49","slug":"linux-fu-making-awk-a-bit-easier","status":"publish","type":"post","link":"http:\/\/cornay.co.za\/wp\/2020\/09\/29\/linux-fu-making-awk-a-bit-easier\/","title":{"rendered":"Linux-Fu: Making AWK a Bit Easier"},"content":{"rendered":"<p><code>awk<\/code> is a kind of Swiss Army knife for text files. However, some of its limitations are often a bit annoying. I&#8217;ve used a simple set of functions to make <code>awk<\/code> a bit better, although I will warn you: it does require GNU extensions to <code>awk<\/code>. That is, you must use <code>gawk<\/code> and not other versions. Your system probably maps <code>\/usr\/bin\/awk<\/code> to something and that something might be <code>gawk<\/code>. But it could also be <code>mawk<\/code> or some other flavor. If you use a Debian-based distro, update-alternatives is your friend here. But for the purposes of this post, I&#8217;m going to assume you are using <code>gawk<\/code>.<\/p>\n<p>By the end of the post, you&#8217;ll see how to use my <a href=\"https:\/\/github.com\/wd5gnr\/awkparse\" target=\"_blank\" rel=\"noopener noreferrer\"><code>awk<\/code> add-on functions<\/a> to split up a line into fields even when there is no single character to separate all fields. In addition, you&#8217;ll be able to refer to the fields using names you decide. You won&#8217;t have to remember that $2 is the time field. You&#8217;ll say <code>Fields_fields[\"time\"]<\/code> instead.<\/p>\n<h2>The Problem<\/h2>\n<p><code>awk<\/code> does a lot of common work for you when you use it to process text files. It reads files a record at a time. Normally, a record is a single line. Then it splits the line on fields using whitespace, or some other choice of field separators. You can write code that manipulates the line or individual fields. This default behavior is great, especially since you can change the end of record character and the field separator. A surprising number of files fit this sort of format.<\/p>\n<p>Until, of course, they don&#8217;t. If you have data coming from a data logging instrument or some database, it could be formatted in a variety of ways. Some fields might have structured data with a variety of separators. This isn&#8217;t a deal-breaker. Since you can get at the whole line, you can do almost anything you want, but the logic is harder and the whole point to using <code>awk<\/code> is to make things easier.<\/p>\n<p>For example, suppose you had a file from a data recorder that had an eight-digit serial number, followed by a six-character tag, and then two floating point numbers separated by colons. The pattern might look like<\/p>\n<pre class=\"brush: plain; title: ; notranslate\">\n^([0-9]{8})([a-zA-Z0-9]{6})([-+.0-9]+),([-+.0-9]+)$\n<\/pre>\n<p>This would be hard to handle with the conventional field splitting and you&#8217;d normally just write code to split everything apart.<\/p>\n<p><span id=\"more-429003\"><\/span><\/p>\n<p>If you have regular fields, but don&#8217;t know how many, you probably want to set <code>FS<\/code> or <code>FPAT<\/code>, instead. We talked about <code>FPAT<\/code> a little before when we were <a href=\"https:\/\/hackaday.com\/2016\/07\/13\/gawking-hex-files\/\">abusing <code>awk<\/code> to read hex files<\/a>. This library is a little different. You can use it to pick apart a line totally. For example, you might have part of the line with a fixed field length and then multiple types of separators. That can be hard to handle with the other methods.<\/p>\n<h2>Regular Expressions<\/h2>\n<p><a href=\"http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg\" target=\"_blank\" rel=\"noopener noreferrer\"><img decoding=\"async\" data-attachment-id=\"210805\" data-permalink=\"https:\/\/hackaday.com\/2016\/06\/21\/gawking-text-files\/gawking-text-blogview\/\" data-orig-file=\"http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg\" data-orig-size=\"800,250\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;2&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;Canon EOS 5D Mark II&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1384010124&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;50&quot;,&quot;iso&quot;:&quot;100&quot;,&quot;shutter_speed&quot;:&quot;0.004&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}\" data-image-title=\"gawking-text-blogview\" data-image-description=\"\" data-medium-file=\"http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg?w=400\" data-large-file=\"http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg?w=800\" class=\"alignright size-thumbnail wp-image-210805 lazyload\" data-src=\"http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/gawking-text-blogview.jpg\" alt=\"\" width=\"250\" height=\"78\" data-srcset=\"http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg 800w, http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg?resize=250,78 250w, http:\/\/cornay.co.za\/wp\/wp-content\/uploads\/2020\/09\/1_gawking-text-blogview.jpg?resize=400,125 400w\" data-sizes=\"auto, (max-width: 250px) 100vw, 250px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 250px; --smush-placeholder-aspect-ratio: 250\/78;\" \/><\/a>To make things easier, I&#8217;ll wrap up the <code>gawk<\/code> <code>match<\/code> function. That function exists in regular <code>awk<\/code>, of course, but <code>gawk<\/code> adds an extension that makes things much easier. Normally, the function performs a regular expression match on a string and tells you where the match starts, if there was a match, and how many characters matched.<\/p>\n<p>With the GNU extensions in <code>gawk<\/code>, you can provide an extra array argument. That array will get some information about the match. In particular, the zero item of the array will contain the entire match. If the regular expression contains sub-expressions in parenthesis, the array will contain those, numbering by the order of the parenthesis. It will also contain start and length information.<\/p>\n<p>For example, if your regular expression were <code>\"^([0-9]+)([a-z]+)$\"<\/code> and your input string is <code>123abc<\/code>, the array would look like this:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\">\n\narray[0] - 123abc\narray[1] - 123\narray[2] - abc\narray[0start] - 1\narray[0length] - 6\narray[1start] - 1\narray[1length] - 3\narray[2start] - 4\narray[2length] - 3\n\n<\/pre>\n<p>You can even have nested expressions, so <code>\"^(([xyz])[0-9]+)([a-z]+)$\"<\/code> with an input of z1x gives <code>array[1]=z1<\/code>, <code>array[2]=z<\/code>, and <code>array[3]=x<\/code>.<\/p>\n<h2>Theory vs Practice<\/h2>\n<p>In theory, that&#8217;s all you need. You can write a regular expression to pick apart a line, parse it, and then access the pieces using the array. In practice, it is much nicer to have everything done so you can use plain names to access the data.<\/p>\n<p>As an example data format, consider a line like this:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\">\n\n11\/10\/2020 07:00 The Best of Bradbury, 14.95 *****\n\n<\/pre>\n<p>There is a date in US format, a time in 24-hour format, an item name, a price, and a rating from 1 to 5 stars that may not be present. Writing a regular expression to grab each field is a bit complex, but not very hard. Here is one way to do it:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\">\n\n&quot;^(([01][0-9])\/([0-3][0-9])\/(2[01][0-9][0-9]))[[:space:]]*(([0-2][0-9]):([0-5][0-9]))[[:space:]]+([^,]+),[[:space:]]*([0-9.]+)[[:space:]]*([*]{1,5})?[[:space:]]*$&quot;\n\n<\/pre>\n<p>That&#8217;s a mouthful, but it works. Note that each item is in parenthesis and some of those are nested. So the date is one field, but the month, day, and year are also fields.<\/p>\n<h2>The Library<\/h2>\n<p>Once you grab the files on <a href=\"https:\/\/github.com\/wd5gnr\/awkparse\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub<\/a>, you could put the fields_* functions into your code. You need to do some setup in the BEGIN tag. Then you process each line using fields_process. Here&#8217;s a small example (with the functions omitted):<\/p>\n<pre class=\"brush: plain; title: ; notranslate\">\n\nBEGIN {\nfields_setup(&quot;^(([01][0-9])\/([0-3][0-9])\/(2[01][0-9][0-9]))[[:space:]]*(([0-2][0-9]):([0-5][0-9]))[[:space:]]+([^,]+),\u00a0 \u00a0 \u00a0[[:space:]]*([0-9.]+)[[:space:]]*([*]{1,5})?[[:space:]]*$&quot;)\nfields_setupN(1,&quot;date&quot;)\nfields_setupN(2,&quot;month&quot;)\nfields_setupN(3,&quot;day&quot;)\nfields_setupN(4,&quot;year&quot;)\nfields_setupN(5,&quot;time&quot;)\nfields_setupN(6,&quot;hours&quot;)\nfields_setupN(7,&quot;minutes&quot;)\nfields_setupN(8,&quot;item&quot;)\nfields_setupN(9,&quot;price&quot;)\nfields_setupN(10,&quot;star&quot;)\n\n}\n\n{\nv=fields_process()\n\n... your code here...\n\n}\n<\/pre>\n<p>In your code you can write something like:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\">\n\ncost=Fields_fields[&quot;price&quot;] * 3\n\n<\/pre>\n<p>Simple, right? The <code>fields_process<\/code> function returns false if there was no match. You can still access the normal <code>awk<\/code> fields like $0 or $2 if you want.<\/p>\n<h2>Inside<\/h2>\n<p>The extra functions rely on two things: the extensions to the <code>gawk<\/code> <code>match<\/code> function and <code>awk<\/code>&#8216;s associative array mechanism. In the past, I&#8217;ve added the named keys to the existing match array so you could get data out either way. However, I&#8217;ve modified it so that the match array is local because I almost never really want that capability and then you have to filter out the extra fields if you want to dump the entire array.<\/p>\n<p>It is frequently useful to start the regular expression with <code>^<\/code> and end it with <code>$<\/code> to anchor the entire string. Just don&#8217;t forget that the regular expression needs to handle white space consumption, as the example does. This is often a benefit when you have fields that can contain spaces, but if you wanted spaces to break fields anyway, you are probably better off with the original parsing scheme.<\/p>\n<p>Another trick is to get &#8220;the rest of the line&#8221; after you parsed off the first fields. You can do that by adding <code>\"(.*)$\"<\/code> to the end of the regular expression. Just don&#8217;t forget to set up a tag for it using <code>fields_setupN<\/code> so that you can fetch the value later.<\/p>\n<p>An easy extension to this library would be to make the pattern an array. The processing function could try each pattern in turn until one matches. Then it would return the index of the matching pattern or false if there were no matches. This would let you define multiple types of lines if you had a complex file format. You&#8217;d probably want to have different sets of field tags for each one, too.<\/p>\n<p>I have a long history of abusing tools like <code>awk<\/code> to do things, like build <a href=\"https:\/\/hackaday.com\/2015\/08\/06\/hacking-a-universal-assembler\/\">cross assemblers<\/a>. Even so, I&#8217;m probably not the <a href=\"https:\/\/hackaday.com\/2016\/01\/15\/wolfenstein-in-600-lines-of-code\/\">worst offender<\/a>.<\/p>\n<p>source <a href=\"https:\/\/hackaday.com\/2020\/09\/29\/linux-fu-making-awk-a-bit-easier\/\">https:\/\/hackaday.com\/2020\/09\/29\/linux-fu-making-awk-a-bit-easier\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>awk is a kind of Swiss Army knife for text files. However, some of its limitations are often a bit annoying. I&#8217;ve used a simple set of functions to make awk a bit better, although I will warn you: it<span class=\"ellipsis\">&hellip;<\/span><\/p>\n<div class=\"read-more\"><a href=\"http:\/\/cornay.co.za\/wp\/2020\/09\/29\/linux-fu-making-awk-a-bit-easier\/\">Read more <span class=\"screen-reader-text\">Linux-Fu: Making AWK a Bit Easier<\/span><span class=\"meta-nav\"> &#8250;<\/span><\/a><\/div>\n<p><!-- end of .read-more --><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[66],"tags":[],"class_list":["post-22958","post","type-post","status-publish","format-standard","hentry","category-news-2"],"_links":{"self":[{"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/posts\/22958","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/comments?post=22958"}],"version-history":[{"count":1,"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/posts\/22958\/revisions"}],"predecessor-version":[{"id":22963,"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/posts\/22958\/revisions\/22963"}],"wp:attachment":[{"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/media?parent=22958"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/categories?post=22958"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/cornay.co.za\/wp\/wp-json\/wp\/v2\/tags?post=22958"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}