0

I have been struggling with the workings of gawk when setting a regexp to FS as space-open_parenthersis OR closing_parenthesis-coma-space I have tried multiple approaches none with the desired behavior 1st. FS="( ()|(), )" 2nd. FS="[( ()(), )]" 3rd(by the ASCII OCT code) FS="[(\040\050)(\051\054\040)]" 4th FS="((\040\050)|(\051\054\040))"

my input file is this https://phpaste.sourceforge.io/demo/paste.php?id=144 it is a file with only one record (line) of my apt-get log in Debian listing some packages.

my gawk program is this

#! /usr/bin/gawk -f
BEGIN {FS = "[(\040\050)(\051\054\040]"}
{
for(i=1;i<=NF;i=i+2) #I increased i by 2 because i want to print the odd numbered fields(only the names of the packages:architecture)  
    print $i
}`

I will execute that in bash as myawk.awk input.txt > output.txt


I will love to include a big word here FXXX!!!! Because I just solved it. I guess as a reward for keep on trying. I used FS = "(\\s\\\050)|(\\\051,\\s)" and that did the trick even tho I don't really understand why three backslashes \\\ before the ASCII oct code.

Would someone offer some explanation on it. Like why?? I've read that AWK read a regex twice and that will require \\ but I needed \\\ (three!!!).

Also any alternative or different approaches will be greatly appreciated!

Thanks in advance!

this is my desired result and thankfully as I got it from my last run https://phpaste.sourceforge.io/demo/paste.php?id=145 (a list of packages with its architecture)

3 Answers 3

1

You may have been overthinking this.  A little.  I got it to work with FS=" \\(|\\), ", and even managed to shorten it to FS=" \\(|), ".

  • You seemed to believe that you needed to do "(regex1)|(regex2)", when all you needed to do was "regex1|regex2".
  • You seemed to believe that, by surrounding parentheses in grouping parentheses, the inner parentheses would become literal, textual parentheses.  It doesn’t work that way.  Regular expression grouping can nest; to treat parentheses as literal, textual parentheses, you need to escape them.
  • ) is special in a regular expression only inside a group.  If the ( is escaped, the ) doesn’t need to be.

This is where it gets tricky.  Naively, from the above, FS=" \(|), " should be good enough.  But GAWK has a problem with regular expressions in string constants; it’s discussed in The GNU Awk User’s Guide, Section 9.1.3.1.  It focuses on getting a literal & in the replacement text for a sub(), gsub(), or gensub() call, but it seems to apply to FS as well:

… there are several levels of escape processing going on.

First, there is the lexical level, which is when awk reads your program and builds an internal copy of it to execute.  Then there is the runtime level, which is when awk actually scans the [program and determines how to execute it].

At both levels, awk looks for a defined set of characters that can come after a backslash.  At the lexical level, it looks for the escape sequences listed in Escape SequencesThus, for every ‘\’ that awk processes at the runtime level, you must type two backslashes at the lexical level.  …

Emphasis (last sentence) added.  What this seems to say is that, if we want to set FS to " \(|), " (to escape the left parenthesis, to treat the parenthesis as a literal, textual parenthesis), you need to assign FS=" \\(|), " or specify -F' \\(|), ' (to escape the backslashes).  You can verify this with a simple test: Run awk -F' \\(|), ', and then print FS from within your program.  It will be displayed as ⁠ \(|), ⁠.


In general, if you want to turn a special character into a non-special character (or, occasionally, vice-versa), the common, traditional approach is to escape it with a \ (backslash).  But there’s another mechanism that’s specific to regular expressions: use a […] expression.  The only characters that are special in […] expressions are ^, - and ] (and that is position-dependent).

  • [pq] means a p or a q
  • [()] means a ( or a )
  • [(p] means a ( or a p
  • [(]  means a ( or … well, since there aren’t any other characters, it just means a literal (.

So, if you’re allergic to backslash(es), you can set FS=" [(]|), ".

2
  • And similarly FS="\\s\\\050|\\\051,\\s" actually sets FS to \s\(|\),\s which matches whitespace (not just SP) literal-leftparen or literal-rightparen comma whitespace Feb 21, 2017 at 10:01
  • First of all, Thank you for throwing some light into my cave. very well explained. I hope my brain will assimilate it as I dive deeper in awk. Thank you for helping. Kinda funny you started with the story of my life!! hehe thanks! You may have been overthinking this. A little.
    – alejo4373
    Feb 21, 2017 at 17:39
0

Here is an alternate approach I came up with. It matches your output exactly. It's probably less efficient because of the additional split() operation for each item, but it is easier to read and understand.

#!/usr/bin/awk -f

BEGIN { 
    FS="), "
}
{
    sub(/^Install:/, "") 
    for (i=1; i<=NF; i++) { 
        split($i, a, " ")
        print a[1]
    }
}
0

There is a much simpler way to accomplish the same task without using awk. You can use Perl regular expressions with many of the grep version that come in the major Linux distributions. With my version of grep (GNU grep version 2.27) the following provides the same output as the awk solution.

grep -oP '(?<=\),).*?(?=\()' input.txt > output.txt

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .