vim nested one-liner loop breaks command

Viewed 130

I want to append a character multiple times for specific lines in a file so all of them would have x characters

Let's say I want all the lines between 20 and 80 to have the same number of characters(50) and the same character appended at the end:

for y in range(20, 80) | if strlen(getline(y)) !=50 | for i in range(50-strlen(getline(y))) | execute y .. 's/$/./' | endfor | endif | endfor

returns E727:Start past end and E1098: String, List or Blob required

if I use it with a specific line it works just fine if strlen(getline(N)) !=50 | for i in range(50-strlen(getline(N))) | execute N .. 's/$/./' | endfor | endif

Eg:

Input:

abcdef
defghijklm
foo

Output:

abcdef**********
defghijklm******
foo*************
3 Answers

Where do those errors come from?

  • E727: Start past end

    This error is caused by the formula 50-strlen(getline(y)) in range(50-strlen(getline(y))) which, when the number of characters on line y is above 50, returns a negative number that :help range() doesn't like.

  • E1098: String, List or Blob required

    This error is a consequence of the previous one: range(50-strlen(getline(y))) returns nothing that can be looped over so :for can't work.

Those errors could probably be avoided by adding more checks and branches to your "one liner" but that doesn't seem like a good idea, if only because your command is already way too complicated for the little it does.

The desired outcome could be achieved more reliably with a much simpler:

:20,80s/$/\=repeat('*',50-strlen(getline('.')))
  • It is a quick :help :substitute with a range, a pattern, and a :help sub-replace-special expression.
  • 20,80 is our range, see :help :range.
  • $ is our zero-width pattern, the end of the line.
  • The very powerful \=<expr> allows us to use the string returned by <expr> as replacement.
  • :help repeat() repeats a given string a given number of times.
  • * is our string.
  • The number of repetitions is calculated with your own formula. When the line is longer than 50, the number of repetitions is negative, which means that there is no repetition and thus that no * is appended to the line.

Before:

a short line
some filler text foo
a line with 59 characters dgsfdsjgdfsjdshdlsjdfshjdsfdlhsjd
what

After:

a short line**************************************
some filler text foo******************************
a line with 59 characters dgsfdsjgdfsjdshdlsjdfshjdsfdlhsjd
what**********************************************

With GNU sed. First append 16 stars and then keep only the first 16 characters.

sed -E 's/$/****************/; s/(.{16}).*/\1/' file

Output:

abcdef**********
defghijklm******
foo*************

Just use awk, it was created for jobs like this:

$ cat file
line 1
line 2
line 3
abcdef
defghijklm
foo
line 7
line 8

$ awk '
    BEGIN { wid=50; pad=sprintf("%*s",wid,""); gsub(/ /,"*",pad) }
    4<=NR && NR<=6 { $0=substr($0 pad,1,wid) }
1' file
line 1
line 2
line 3
abcdef********************************************
defghijklm****************************************
foo***********************************************
line 7
line 8

Change the width (50 above), and start/end line numbers (4 and 6 above) to suit.

Related