Nick
Nick

Reputation: 4248

Perl regex one liner not working correctly

I'm trying to do the following in a bash script to a file:

Before:

{
    "main": "3.3.0",
    "archive":[
        "3.2.2"
    ]
}

After:

{
    "main": "3.3.1",
    "archive":[
        "3.3.0",
        "3.2.2"
    ]
}

using this perl one liner:

perl -pi -e "s/\"main\"\: \"(.*?)(\".*)\[/\"main\": \"${version}\2\[\n\t\t\"\1\",/s" "$json"

Where $version holds the new version number and $json is the path to the file.

It seems perl doesn't match, and I can't understand why. Removing the \[ does match, but I can't see what's wrong with that literal.

Upvotes: 0

Views: 213

Answers (3)

oliv
oliv

Reputation: 13239

Using jq to both move the main version number to the array archive and increment the last digit of the main version:

jq '.archive += [.main] |
    .main |= (split(".") | .[-1] |= (tonumber+1|tostring) | join("."))' file

The operator += adds a new value to the array archive.

The operator |= assigns a new value to main. The last element of the splitted string is incremented by 1 and then reassembled.

Upvotes: 1

zdim
zdim

Reputation: 66873

Assuming that you can fix your JSON (a trailing comma is not allowed) I'd recommend to process this using a module, and in a script.

An example with JSON, wrapped in a "one"-liner as asked

perl -MPath::Tiny -MJSON -0777 -wnE'
    my $hr = decode_json $_; 
    unshift @{$hr->{archive}}, $hr->{main}; 
    $hr->{main} =~ s/[0-9]+\.[0-9]+.\K([0-9]+)/$1+1/e; 
    path("new_".$ARGV)->spew(encode_json $hr)' 
' data.json

The very handy Path::Tiny is used to easily dump JSON output. I make the output file name by prefixing new_ to the input filename (available in $ARGV variable), adjust as suitable.

If installing a module is a problem for some reason you can instead simply print the JSON encoded string and redirect the output

perl -MJSON -0777 -wnE'
    my $hr = decode_json $_; 
    unshift @{$hr->{archive}}, $hr->{main}; 
    $hr->{main} =~ s/[0-9]+\.[0-9]+.\K([0-9]+)/$1+1/e; 
    say encode_json $hr 
' data.json > new_data.json

These produce the output file with   {"archive":["3.3.0","3.2.2"],"main":"3.3.1"}

With the -0777 command switch the whole file is "slurped" into a scalar ($_) and -M... loads the given module. Then we use decode_json, which JSON exports by default in its functional interface, to obtain a hashref with data.

The current value of main is then added to the beginning of the arrayref in archive using unshift, and is then changed by bumping up its release/patch number, using a regex.

Finally the encode_json, also exported in JSON's functional interface, is used to JSON-encode the hashref, what is dumped either with Path::Tiny to a file or to STDOUT for redirection.


A word on the number of existing JSON modules is in order, prompted by comment by Grinnz.

The JSON linked above will load the JSON::XS module, and if that is not installed then it falls back to the compatible pure-Perl one, JSON::PP. I'd recommend the XS module, which is much faster and proven in wide use.

Another option is Cpanel::JSON::XS, the JSON::XS fork with a bug tracker and a list of bug fixes. I've happily used both without any issues.

To have these tried in order Cpanel:: then ::XS then ::PP, also with some improvements over JSON in how backends are loaded, use JSON::MaybeXS.

Note that none of these are in core.

Upvotes: 3

Shawn
Shawn

Reputation: 52336

(now that Ihis question has been reopened and I can post an answer..)

Assuming you fix your data so it's valid JSON (Note the invalid trailing comma), this is an easy jq one-liner:

$ jq "{main: \"$version\", archive: [ .main, .archive[] ]}" "$json" > new.json
$ mv -f new.json "$json"

Just like with HTML and XML, using regular expressions to try to manipulate JSON is a mistake. Use a more appropriate tool.

Upvotes: 1

Related Questions