How to copy one key:value one block underneath and repeat this for each following blocks?
Question:
I have a yaml file where i need to copy a key:value (when: var) to the task underneath and need to repeat that for each block.
How it looks like now:
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
How i need it to look like:
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
From researching how to do this, i think a vim loop could do that but i have no clue how to do that.
In case anybody has any hints, i’d be very grateful.
Edit: What i tried.
With this i captured the values i needed. (the var_ values)
grep -e set_fact -B1 filterlist_playbook.yml|grep -e "^.*:"|grep -v "set_fact:"|awk -F":" '{print $1}'
I tried this in two different ways.
1 first add the "when:" field and then copy/paste the var_ value
Adding the when: field was ok with sed. Pasting the right value sequentially… i do not find info how to do that, my colleagues also do not know.
2 copy/paste the "when: var_" value to the block underneath.
Again capturing the right value is ok with sed, pasting it sequentially leaves me scratching my head and googling into oblivion.
Note:
I will definitely select a working answer but its taking me time to try each one of them.
Answers:
Using awk
you can do this:
awk -F ': ' '
$1 ~ / when$/ {var = $0}
NR > 1 {print prev}
!NF && prev ~ /^- task/ {print var}
{prev = $0}
END {
print prev
if (prev ~ /^- task/)
print var
}' file
Output:
- set_fact: something
- set_fact: somethingelse
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: somethingelse
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: somethingelse
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
In vim you could do this:
qq
start recording a macro called q
/when:
Enter find the next occurrence of when:
yy
yank the current line
j
move the cursor down
p
paste the line underneath
q
terminate the macro recording
At this point you can call the macro as many times as you need typing @q
.
You may also get to know in advance how many times you need to perform this finding the occurrences of when:
with :%s/when://ng
, which will print out something like 3 matches on 3 lines
. Then you may just type e.g. 3@q
to call the macro 3 times.
If ed
is available/acceptable.
printf '%sn' 'g/^[[:space:]]{1,}when: var_.*/t.1' ,p Q | ed -s file.yaml
If the output is correct, and in-place editing is needed, change
,p Q
to
w q
I only have GNU ed
at hand.
-
g
Globally substitute for all non-overlapping instances of the RE rather than just the first one. If both g and count are specified, the results are unspecified.
-
(1,$)g
/RE/command list
-
(.,.)t
address
The t
command shall be equivalent to the m
command, except that a copy of the addressed lines shall be placed after address address (which can be 0); the current line number shall be set to the address of the last line added.
-
.
The period character ( .
) shall address the current line.
-
The positive decimal number n shall address the nth line of the edit buffer.
-
,
or %
Depending on the variant shall address the whole buffer.
,p
or %p
-
(.,.)p
The p
command shall write to standard output the addressed lines; the current line number shall be set to the address of the last line written. The p command can be appended to any command other than e, E, f, q, Q, r, w, or !.
-
w
The w
command shall write the addressed lines into the file named by the pathname file.
-
q
The q command shall cause ed to exit. If the buffer has changed since the last time the entire buffer was written, the user shall be warned, as described previously.
-
Q
The Q
command shall cause ed to exit without checking whether changes have been made in the buffer since the last w command.
-
-s
Suppress the writing of byte counts by e, E, r, and w commands and of the ‘!’ prompt after a !command.
See
In vim
something like.
:g/^[[:space:]]*when: var_.*/t.1
Here is how I would do it in Vim:
:g/when/normal mzvip^[:'zt'>^M
with ^[
obtained with <C-v><Esc>
and ^M
obtained with <C-v><CR>
.
Breakdown:
:g/<pattern>/<command>
executes <command>
on every line that matches <pattern>
.
:normal xxx
executes normal commands from command-line mode.
mz
puts mark 'z
on the matched line.
vip
visually selects the current paragraph, placing mark '>
on its last line.
<Esc>
leaves visual mode.
:'zt'>
copies line with mark 'z
below line with mark '>
.
<CR>
executes that last command.
See :help :global
, :help :normal
, :help mark-motions
, :h :t
, and :h :range
.
Note that, intuitively, using mark '}
like this:
:g/when/t'}
looks like it would solve the problem neatly, but Vim puts mark '}
on the first blank line after an actual paragraph, which doesn’t help us since our anchor is the last line of the actual paragraph.
That is one of the reasons why the vip
dance is necessary: we need a mark on the last line of the paragraph, not after it, and visually selecting the "inner paragraph" does just that with mark '>
.
{m,g}awk '
BEGIN { _*= FS = "^" (OFS = " when: ") (___="")
} !_<NF ? (__=$_)*(NF=_) : ___<__ ? $!NF=__ RS $_ RS __ (__=___) : !_'
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
To get the output you show from the input you show all you need is this, using any awk:
$ awk -v RS= -F'n' '{print $0 ORS $(NF-1) ORS}' file
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
or if your real input is more complicated than you’ve shown then, also using any awk:
$ awk '/when:/{w=$0} {print} /task:/ && w{print w; w=""}' file
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
I have a yaml file where i need to copy a key:value (when: var) to the task underneath and need to repeat that for each block.
How it looks like now:
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
How i need it to look like:
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
From researching how to do this, i think a vim loop could do that but i have no clue how to do that.
In case anybody has any hints, i’d be very grateful.
Edit: What i tried.
With this i captured the values i needed. (the var_ values)
grep -e set_fact -B1 filterlist_playbook.yml|grep -e "^.*:"|grep -v "set_fact:"|awk -F":" '{print $1}'
I tried this in two different ways.
1 first add the "when:" field and then copy/paste the var_ value
Adding the when: field was ok with sed. Pasting the right value sequentially… i do not find info how to do that, my colleagues also do not know.
2 copy/paste the "when: var_" value to the block underneath.
Again capturing the right value is ok with sed, pasting it sequentially leaves me scratching my head and googling into oblivion.
Note:
I will definitely select a working answer but its taking me time to try each one of them.
Using awk
you can do this:
awk -F ': ' '
$1 ~ / when$/ {var = $0}
NR > 1 {print prev}
!NF && prev ~ /^- task/ {print var}
{prev = $0}
END {
print prev
if (prev ~ /^- task/)
print var
}' file
Output:
- set_fact: something
- set_fact: somethingelse
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: somethingelse
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: somethingelse
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
In vim you could do this:
qq
start recording a macro calledq
/when:
Enter find the next occurrence ofwhen:
yy
yank the current linej
move the cursor downp
paste the line underneathq
terminate the macro recording
At this point you can call the macro as many times as you need typing @q
.
You may also get to know in advance how many times you need to perform this finding the occurrences of when:
with :%s/when://ng
, which will print out something like 3 matches on 3 lines
. Then you may just type e.g. 3@q
to call the macro 3 times.
If ed
is available/acceptable.
printf '%sn' 'g/^[[:space:]]{1,}when: var_.*/t.1' ,p Q | ed -s file.yaml
If the output is correct, and in-place editing is needed, change
,p Q
to
w q
I only have GNU ed
at hand.
-
g
Globally substitute for all non-overlapping instances of the RE rather than just the first one. If both g and count are specified, the results are unspecified.
-
(1,$)
g
/RE/command list -
(.,.)
t
addressThe
t
command shall be equivalent to them
command, except that a copy of the addressed lines shall be placed after address address (which can be 0); the current line number shall be set to the address of the last line added. -
.
The period character (
.
) shall address the current line. -
The positive decimal number n shall address the nth line of the edit buffer.
-
,
or%
Depending on the variant shall address the whole buffer.
,p
or%p
-
(.,.)
p
The
p
command shall write to standard output the addressed lines; the current line number shall be set to the address of the last line written. The p command can be appended to any command other than e, E, f, q, Q, r, w, or !. -
w
Thew
command shall write the addressed lines into the file named by the pathname file. -
q
The q command shall cause ed to exit. If the buffer has changed since the last time the entire buffer was written, the user shall be warned, as described previously.
-
Q
The
Q
command shall cause ed to exit without checking whether changes have been made in the buffer since the last w command. -
-s
Suppress the writing of byte counts by e, E, r, and w commands and of the ‘!’ prompt after a !command.
See
In vim
something like.
:g/^[[:space:]]*when: var_.*/t.1
Here is how I would do it in Vim:
:g/when/normal mzvip^[:'zt'>^M
with ^[
obtained with <C-v><Esc>
and ^M
obtained with <C-v><CR>
.
Breakdown:
:g/<pattern>/<command>
executes<command>
on every line that matches<pattern>
.:normal xxx
executes normal commands from command-line mode.mz
puts mark'z
on the matched line.vip
visually selects the current paragraph, placing mark'>
on its last line.<Esc>
leaves visual mode.:'zt'>
copies line with mark'z
below line with mark'>
.<CR>
executes that last command.
See :help :global
, :help :normal
, :help mark-motions
, :h :t
, and :h :range
.
Note that, intuitively, using mark '}
like this:
:g/when/t'}
looks like it would solve the problem neatly, but Vim puts mark '}
on the first blank line after an actual paragraph, which doesn’t help us since our anchor is the last line of the actual paragraph.
That is one of the reasons why the vip
dance is necessary: we need a mark on the last line of the paragraph, not after it, and visually selecting the "inner paragraph" does just that with mark '>
.
{m,g}awk ' BEGIN { _*= FS = "^" (OFS = " when: ") (___="") } !_<NF ? (__=$_)*(NF=_) : ___<__ ? $!NF=__ RS $_ RS __ (__=___) : !_'
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
To get the output you show from the input you show all you need is this, using any awk:
$ awk -v RS= -F'n' '{print $0 ORS $(NF-1) ORS}' file
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c
or if your real input is more complicated than you’ve shown then, also using any awk:
$ awk '/when:/{w=$0} {print} /task:/ && w{print w; w=""}' file
- set_fact: something
- set_fact: var_a
- task: do the foo
when: var_a
- task: do the goo
when: var_a
- set_fact: something
- set_fact: var_b
- task: do the fofo
when: var_b
- task: do the gogp
when: var_b
- set_fact: something
- set_fact: var_c
- task: do the fooolo
when: var_c
- task: do the gooolo
when: var_c