How to get specific data from a data block based on a condition

I have a file like this:

[group] enable = 0 name = green test = more [group] name = blue test = home [group] value = 48 name = orange test = out 

Between the label and = and the value there can be one or more spaces / tabs.
The number of rows can be careful in each block.
I like to have name only if it is not. enable = 0

Thus, the output should be:

 blue orange 

Here is what I managed to create:

 awk -v RS="group" '!/enable = 0/ {sub(/.*name[[:blank:]]+=[[:blank:]]+/,x);print $1}' blue orange 

There are several errors in this:

  • I canโ€™t install RS on [group] and this does not work RS="[group]" and RS="\[group\]" . Then this will happen if name or other labels contain group .
  • I prefer not to use RS with multiple characters, as this is just gnu awk .

Anyone have any other suggestion? sed or awk and do not use a long chain of commands.

+6
source share
7 answers

If you know that groups are always separated by blank lines, set RS to an empty line:

 $ awk -v RS="" '!/enable = 0/ {sub(/.*name[[:blank:]]+=[[:blank:]]+/,x);print $1}' blue orange 

@devnull explained in his answer that GNU awk also accepts regular expressions in RS , so you can only split into [group] if it is on a separate line:

 gawk -v RS='(^|\n)[[]group]($|\n)' '!/enable = 0/ {sub(/.*name[[:blank:]]+=[[:blank:]]+/,x);print $1}' 

This ensures that we do not split evil names like

 [group] enable = 0 name = [group] name = evil test = more 
+5
source

Your problem:

I canโ€™t install RS on [group] and this does not work RS="[group]" and RS="\[group\]" .

Saying:

 RS="[[]group[]]" 

should give the desired result.

+5
source

In such situations, when there are explicit name = value operators in the record, I like to fill the array with these mappings first, for example:

 map["<name>"] = <value> 

and then just use the names to refer to the values โ€‹โ€‹I want. In this case:

 $ awk -v RS= -F'\n' ' { delete map for (i=1;i<=NF;i++) { split($i,tmp,/ *= */) map[tmp[1]] = tmp[2] } } map["enable"] !~ /^0$/ { print map["name"] } ' file blue orange 

If your version of awk does not support deleting an entire array, change delete map to split("",map) .

Compared to using RE and / or sub () s, etc., this makes the solution more reliable and extensible if you want to compare and / or print the values โ€‹โ€‹of other fields in the future.

+5
source

Since you have line-delimited entries, you should consider placing awk in paragraph mode. If you need to check the [group] identifier, just add code to process it. Here is an example of code that should meet your requirements. Run as:

 awk -f script.awk file.txt 

The content of script.awk :

 BEGIN { RS="" } { for (i=2; i<=NF; i+=3) { if ($i == "enable" && $(i+2) == 0) { f = 1 } if ($i == "name") { r = $(i+2) } } } !(f) && r { print r } { f = 0 r = "" } 

Results:

 blue orange 
+3
source

This may work for you (GNU sed):

 sed -n '/\[group\]/{:a;$!{N;/\n$/!ba};/enable\s*=\s*0/!s/.*name\s*=\s*\(\S\+\).*/\1/p;d}' file 

Read the [group] block in the template space, then replace the color if the enable variable is not set to 0 .

  • sed -n '...' set sed to run in silent mode, without output, unless specified, for example, p or p
  • /\[group\]/{...} when we have a line containing [group] , do what is inside the braces.
  • :a;$!{N;/\n$/!ba} To make a loop, we need a place for the loop,: :a is the place for the loop. $ is the end of the file address, and $! doesnโ€™t mean the end of the file, so $!{...} means doing what is inside the braces when itโ€™s not the end of the file. N means adding a new line and the next line to the current line and /\n$/ba when we have a line ending in empty branch ( b ) on a . Thus, it collects all lines from a line containing `[group] into an empty line (or the end of the file).
  • /enable\s*=\s*0/!s/.*name\s*=\s*\(\S\+\).*/\1/p if the collected lines contain enable = 0 , then do not replace color. Or, to put it another way, if the lines collected so far do not contain enable = 0 , replace the color.
+3
source

If you do not want to use a record separator, you can use a dummy variable as follows:

 #!/usr/bin/awk -f function endgroup() { if (e == 1) { print n } } $1 == "name" { n = $3 } $1 == "enable" && $3 == 0 { e = 0; } $0 == "[group]" { endgroup(); e = 1; } END { endgroup(); } 
+2
source

You can use bash for this.

 while read line; do if [[ $line == "enable = 0" ]]; then n=1 else n=0 fi if [ $n -eq 0 ] && [[ $line =~ name[[:space:]]+=[[:space:]]([az]+) ]]; then echo ${BASH_REMATCH[1]} fi done < file 

This will work, however, if enable = 0 there is always only one line above the line with name .

+1
source

All Articles