0

I have the following string on a Linux-based synology platform, and I want to extract some values:

{"report":"Instantaneous values:<BR>voltage=243.5 Vrms<BR>FFTComponents:<BR>Phase 1:<BR>\tcurrent=0.348 A, activePower=68.461 W, reactivePower=50.175 var, apparentPower=84.879 VA, cosfi=80, quadrant=0, phaseshift=0.0, phaseDiff=0.0<BR>\tFFTComponents:<BR>Phase 2:<BR>\tcurrent=0.076 A, activePower=2.888 W, reactivePower=18.492 var, apparentPower=18.717 VA, cosfi=10, quadrant=0, phaseshift=0.0, phaseDiff=0.0<BR>\tFFTComponents:<BR>Phase 3:<BR>\tcurrent=1.431 A, activePower=299.807 W, reactivePower=177.96 var, apparentPower=348.646 VA, cosfi=85, quadrant=0, phaseshift=0.0, phaseDiff=0.0<BR>\tFFTComponents:<BR><BR><BR>Phase 1, peak active power 5570.098 W at 03/09/2022 14:18:10<BR>Phase 2, peak active power
4562.172 W at 25/09/2022 09:21:45<BR>Phase 3, peak active power 3188.103 W at 07/11/2022 16:35:35<BR>active energy RMS per phase mapping combination<BR>phase mapping 210=372.779 kWh [ 1/1]<BR>phase mapping 12=808.956 kWh [* 1/3]<BR>phase mapping 21=307.154 kWh [
-1/1]<BR>phase mapping 102=321.293 kWh [ -1/2]<BR>phase mapping 120=508.832 kWh [ 1/0]<BR>phase mapping 201=317.701 kWh [
-1/1]<BR><BR>active energy RMS (solar) per phase mapping combination<BR>phase mapping 210=0.0 kWh [ 1/1]<BR>phase mapping 12=0.0 kWh [* 1/3]<BR>phase mapping 21=0.0 kWh [ -1/1]<BR>phase mapping 102=0.0 kWh [ -1/2]<BR>phase mapping 120=0.0 kWh [ 1/0]<BR>phase mapping 201=0.0 kWh [ -1/1]<BR><BR>"}

I found some code on the internet. The code works, however, it does not give me all the information I want.

https://github.com/apazga/smappee-domoticz-bash/blob/master/smappee_bash_extractor.sh

The part of the code uses sed to find the string urrent=, and return the value after this string.

AMPS=`echo $SMAP |sed -e 's|.*urrent=\(.*\)|\1|' -e 's|\(.\{1,4\}\).*|\1|'`

I would like to split this out into AMPSL1, AMPSL2 and AMPSL3

  • AMPL1: must the search for the first occurrence of current and return 0.348
  • AMPL2: must the search for the second occurrence of current and return 0.076
  • AMPL3: must the search for the third occurrence of current and return 1.431

I already found out that following code returns the last occurrence

  AMPSL3=`echo $SMAP |sed -e '$s|.*urrent=\(.*\)|\1|' -e 's|\(.\{1,4\}\).*|\1|'`

Can someone please help me out here.

3
  • 1
    I guess you mean "third" for AMPS3? The immediate problem is that .* matches the entire rest of the string; you want to restrict this regex to match only ... what you want to match. More fundamentally, trying to parse free-form HTML fragments out of JSON seems like a position you would like to avoid ending up in in the first place.
    – tripleee
    Nov 15, 2022 at 10:39
  • What operating system are you using?
    – terdon
    Nov 15, 2022 at 10:58
  • This is a linux based system (Synology). That read the string from a energy monitor called "Smappee". The AMPSL3 must indeed return the third occurance. I il change in the text. Thanks for that. Is there an easy way to do this with the code supplied? Can yo please give an example? I have never heard of SED before viewing and trying to extend this code.
    – DKY
    Nov 15, 2022 at 11:01

2 Answers 2

1

The problem with regular expressions is they cannot count. So you would need a different, complicated, regular expression for each value you want to extract. Instead, I would use grep before sed to isolate the desired values:

$ AMPS=$(echo "$SMAP" | grep -oE 'current=[0-9]+\.[0-9]+' | sed -E 's|current=||')
$ echo "$AMPS"
0.348
0.076
1.431

Then combinations of head and tail can be used to extract the individual values.

$ AMPSL1=$(echo "$AMPS" | head -1)
$ echo $AMPSL1
0.348
$ AMPSL2=$(echo "$AMPS" | tail +2 | head -1)
$ echo $AMPSL2
0.076
$ AMPSL3=$(echo "$AMPS" | tail +3 | head -1)
$ echo $AMPSL3
1.431

Or as terdon suggests, "you could avoid the double head/tail if you use awk".

$ AMPSL2=$(echo "$AMPS" | awk 'NR==2')
$ echo $AMPSL2
0.076
5
  • Note that the OP is using a synology system, so will likely not have a grep that supports -o.
    – terdon
    Nov 15, 2022 at 11:28
  • I don't know what version they were introduced, but busybox grep has -o and -E available, but not -P.
    – xiota
    Nov 15, 2022 at 11:34
  • Ah, OK thanks. Fair enough, busybox is indeed the most likely one there.
    – terdon
    Nov 15, 2022 at 11:36
  • 1
    You could avoid the double head/tail if you use awk, by the way. Something like AMPL2=$(echo "$AMPS" | awk 'NR==2').
    – terdon
    Nov 15, 2022 at 11:47
  • @terdon grep on my Synology NAS is GNU grep 3.1 from 2017, which supports -o. The user does not say what Synology system they are using though...
    – Kusalananda
    Nov 15, 2022 at 15:31
0

If you have GNU grep (you might not, on an embedded system), you can do this:

read ampl1 ampl2 ampl3 < <(grep -oP 'current=\K[0-9.]+' <<<"$amps" | tr '\n' ' ' | sed 's/$/\n/' )

The read var < <(command) idiom allows you to save the output of command into the variable var in your current shell using process substitution.

That is using grep -o to print only the matching part of the input, and -P for PCRE regular expressions which give us the \K symbol for "forget everything matched until here". We then need to convert spaces to newlines with tr and add a trailing newline with sed to get everything ready to be passed to the read builtin that stores into variables. The output is:

$ read ampl1 ampl2 ampl3 < <(grep -oP 'current=\K[0-9.]+' <<<"$amps" | tr '\n' ' ' | sed 's/$/\n/' )
$ echo "AMPL1: $ampl1 AMPL2: $ampl2 AMPL3: $ampl3"
AMPL1: 0.348 AMPL2: 0.076 AMPL3: 1.431

If you can't use grep -oP, you can do something like this instead:

$ read ampl1 ampl2 ampl3 < <(perl -007 -ne '@m=(/current=([0-9.]+)/g); print "@m\n"' a)
$ echo "AMPL1: $ampl1 AMPL2: $ampl2 AMPL3: $ampl3"
AMPL1: 0.348 AMPL2: 0.076 AMPL3: 1.431

This answer requires you to use the bash shell. Since the default shell on Synology systems is the bash shell running in POSIX mode, process substitutions (<(...)) are not enabled. You would have to first start bash by simply typing bash.

Or, if you will be using these commands in a script, run the script with bash scriptName.sh.

You must log in to answer this question.

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