Reputation: 583
I need to add an ID property to a list of objects in a JSON file. I found this useful in-line Perl script that replaces each match with a random int.
:%! perl -pne 's/XYZ/int(rand 1000)/ge'
Assuming I don't know the list's length in advance, how would I replace something like this
{
"id" : XYZ
},
{
"id" : XYZ
},
{ ... }
with this:
{
"id" : 1
},
{
"id" : 2
},
{ ... }
I've tried this:
:%! perl -pne 's/XYZ/(0..100)/ge'
but that assumes I know the length of the list in advance and also does not work as expected. I know I can get a number of matches using something like this:
:%s/"id" : XYZ//gn
Upvotes: 0
Views: 1320
Reputation: 241768
-p
already processes the input line by line, so -n
is redundant.
perl -pe 's/XYZ/$i++/ge'
$i++
returns the value of $i
in numeric context and adds 1 to it.
Upvotes: 3
Reputation: 172520
For those looking for a pure Vim solution, here's a more canonical and general solution than in @sidyll's answer that doesn't require :perldo
nor :global
, and would also work with multiple matches in a line (adding the g
flag to :substitute
):
In Vim, you can use :help sub-replace-expression
to replace text with arbitrary expressions. Unfortunately, incrementing a variable is a statement in Vim, not an expression. Either one has to resort to tricks like using the length of a List as the counter (add()
can be used in an expression):
:let c = [] | %substitute/XYZ/\=len(add(c, 0))/
Or you have to define a separate function (once):
function! Increment()
let g:i += 1
return g:i
endfunction
let g:i = 0 | %substitute/XYZ/\=Increment()/
The expression syntax is a bit weird (but if you're into Perl, you're used to much worse :-). Also, the explicit separate initialization of the counter (and function) is cumbersome. My PatternsOnText plugin improves on the built-in :substitute
command with several variants. Among them :SubstituteExecute
, which benefits from a predefined context object:
:%SubstituteExecute /XYZ/ let v:val.n += 1 | return v:val.n
Renumbering is such a frequent task that the plugin even has a special command for it:
:Renumber /XYZ/
xmlstarlet
for XML and jq
for JSON instead of grep
and sed
).Upvotes: 2
Reputation: 59277
If you are doing this in Vim and you have the +perl
feature then:
:%perldo s/XYZ/++$i/e
If you want to do it with plain Vim commands, you can follow
the same logic and use a variable. However, it needs to be
created beforehand and incremented after each substitution.
Thus you may need a help from :g
to execute two commands:
:let i=0
:g/XYZ/let i+=1|s//\=i
The empty s//
reuses the pattern from :g
.
Alternatively, just filter it through Perl as you did originally:
:%!perl -pe 's/XYZ/++$i/e'
Upvotes: 4
Reputation: 9231
Using Perl's built in JSON parser (in 5.14+) you don't need to deal with strings that may show up elsewhere, whether strings and numbers should be quoted or not, or other complications, you can just modify the JSON as a data structure (code will probably need to be altered to modify the correct part of your full JSON structure):
perl -0777 -MJSON::PP -E'my $data = decode_json readline; $_->{id} = ++$i foreach @$data; print encode_json $data'
Even better would be to use JSON::MaybeXS as that will install and use a much faster parser by default.
The -0777
switch is a convention for one-liners to cause readline or the <>
operator (also used by the -p switch) to return the whole input at once.
Upvotes: 3