3

I'm parsing data in the following format:

prop1=value1:prop2=value2:prop3=value3+prop1=value4:prop2=value5
  1. parts of the string are delimited by +
  2. properties can appear in any order
  3. the desired output is the value of prop2 from the string part where prop1 has a particular value (input)

Can I achieve this through standard unix command-line tools, or do I have to write a small C program?

Edit - for the line shown, this is the desired functionality:

input: value1 -> output: value2
input: value4 -> output: value5
4
  • 1
    Is this a multi line parse ? Could you show a couple of lines of input and required output ?
    – X Tian
    Mar 11, 2014 at 12:30
  • Moreover, do not expect answers to question written in a rather lousy manner.
    – devnull
    Mar 11, 2014 at 12:31
  • It is not clear what you want to do. Could you indicate the desired output?
    – fedorqui
    Mar 11, 2014 at 12:31
  • @fedorqui Is it better now? Mar 11, 2014 at 12:37

7 Answers 7

1

Based on devnull's answer I put together this:

echo $LINE | tr '+' '\n' | grep "prop1=$VALUE" | tr ':' '\n' | grep "prop2=" | cut -d= -f2

I'm still open to any better answers.

2
  • I'm sure a awk master could find an easy onliner for that :D
    – Kiwy
    Mar 11, 2014 at 12:44
  • Great! - the power of the pipe! - though it needs \(:\|$\) after $VALUE, otherwise when VALUE=value1 and source string has prop1=value10, then it yields a false positive - `$VALUE(:\|$)`` will get around that for even multi word property values.
    – Peter.O
    Jun 1, 2015 at 17:47
1

You can use gawk:

awk -F'+' '{for(i=1;i<=NF;i++){if($i ~ /value1/)
               {$i=gensub(/.*prop2=(.*)(:.*|.*$)/,"\\1","g",$i);print $i}}}' file
1
  • Oops! I +1'd the answer then tested further. It fails if a "part" has a prop1=your-value, but does not have a prop2=
    – Peter.O
    Jun 1, 2015 at 15:23
1

If you don't mind Perl:

perl -053nE '
    BEGIN{ $value = shift }
    chomp;
    tr {=:}{ };
    %h = eval "qw($_)";
    say $h{prop2} if $h{prop1} eq $value
' your_file value1

This doesn't assume any particular order of properties, but it does assume that neither property names nor values will contain spaces. If that's not true, some more parsing would be required.

Explanation

  • -053 sets the record separator to the ASCII character whose octal code is 53, namely +.
  • -n means apply work on one record at a time, aliasing $_ to the record content.
  • -E means execute the following code with the previous switches in mind.

  • The code with comments:

    perl -053nE '
        BEGIN{ $value = shift } # $value now = the command line argument
        chomp;                  # Remove the record separator (+)
        tr {=:}{ };             # Make all '=' and ':' into a space
        %h = eval "qw($_)";     # Parse the line into a hash (explained below)
        say $h{prop2} if $h{prop1} eq $value # This is your required logic
    ' your_file value1
    
    • The qw() operator takes a space-separated list and properly quotes its constituent words to create a list. When applied to the record

      prop1 value1 prop3 value3 prop2 value2
      

      (remember how we changed all + and = into a space), it transforms the record into a list. When this list is assigned to a hash variable %h, it is assumed that the hash keys are the odd numbered elements of the list and the hash values are the even numbered ones.

2
  • This works, but with a minor catch: When there is a prop1=your_value but no prop2=, it prints a blank line... Nice explanation btw ... I've got pretty minimal working knowledge of perl, but I think that another feature of -n is that it inhibits auto-printing.
    – Peter.O
    Jun 1, 2015 at 15:39
  • @Peter.O There is no "auto-printing" in Perl. Perl has two modes for working on a file record-by-record: -n (no printing) and -p (printing); so it's always explicitly specified.
    – Joseph R.
    Jun 1, 2015 at 17:04
0

Assuming that you will always want the first prop1 after whichever value you pass, you could do:

$ perl -lne '/prop1=value1[:+].*?prop2=([^:+]+)/ && print $1' file 
value2
$ perl -lne '/prop1=value4[:+].*?prop2=([^:+]+)/ && print $1' file 
value5

The idea here is to match prop1=valueN. The value4[:+] makes sure that the entire value is checked, up to the next field delimiter (: or +) so that value4 does not match value44 for example. Then, 0 or more characters (.*?), followed by prop2= and the value of prop2.

0

You can try something like:

awk '{
    for(i = 1; i <= NF; i++) {
        split($i, tmp, /=/)
        if(tmp[1] == "prop1") {
            printf " %s%s", "input: ",tmp[2]
        }
        if(tmp[1] == "prop2") {
            printf " %s%s", "output: ",tmp[2]
        }
     }
     print ""
}' RS='+' FS=':' file

Output (with your sample input):

input: value1 output: value2
input: value4 output: value5
0
awk -F"[:=]" -vRS='+' '{for(i=1; i<=NF; i++) {if($i == "prop1") I=$(i+1); if($i == "prop2") O=$(i+1)}; printf "input: %s -> output: %s\n", I, O}' file
0
awk -vRS='+|\n' '
    BEGIN{ p1=":prop1="; p2=":prop2=" } 
    { if( ":"$0":" ~ p1 p1val ":" ) {
        if( match(":"$0":", p2 "[^:]+:" ) ) {
          print substr($0, RSTART+length(p2)-1, RLENGTH-length(p2)-1)
    }}}' p1val=value4

Pipe your string (or file) into awk.
The value of prop1 is input via an arg-variable p1val= as shown.

Output for input: p1val=value1

value2

Output for input: p1val=value4

value5

You must log in to answer this question.

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