7

I have a text file of numbered entries:

1. foo
2. bar 100%
3. kittens
4. eat cake
5. unicorns
6. rainbows

and so on up to some large number. Then after an empty line, a new block starts from 1.

I insert a new entry, replacing, say, 4. and I need to renumber all the subsequent entries in the block:

1. foo
2. bar 100%
3. kittens
4. sunshine <
5. eat cake
6. unicorns
7. rainbows
  • 2
    Are blocks contiguous, or is there a separator? (Even an empty line?) – Stephen Kitt Aug 16 '16 at 15:54
5

VIM solutions

There's two solutions: one is via automating Ctrla keypress over a selection, second is via executing a pattern replacement with submatch(0)+1 over the selection. First the key automation.

Start by creating your list:

1. foo
2. bar 100%
3. kittens
4. eat cake
5. unicorns
6. rainbows

Insert an entry

1. foo
2. bar 100%
3. kittens
4. eat cake
4. sunshine
5. unicorns
6. rainbows

Position your cursor onto 4. sunshine and from command mode press shift + v, then shift + g . This is visual selection till the end of file. You can also move the cursor to the end of a block in the usual ways.

Press : to enter command mode, and you will see this: :'<,'> . Now type in the following:

norm Ctrl+V Ctrl+A

What Ctrl-v and ctrl-A do, is they allow you to enter "exact" key, so it will change into ^A , highlighted. This is basically saying for all lines selected, execute in normal mode keypress Ctrl-A , and Ctrl-A by default increments the number under cursor. You will see the numbers change

Solution in action:

Before

enter image description here

After

enter image description here


Another way would be to select everything from the first repeated number like before( Shiftv, then G ), and go into command mode to execute:

:'<,'>s/\v(^\d+)\./\=(submatch(0)+1).'.'/ 
  • This is exactly the simple memorable answer that I was searching for and couldn't figure out! – Zanna Aug 28 '16 at 22:19
  • I can't use/type Ctrl-A in vim because cntrl-A is mapped to another key for tmux. Is there a way to send the same command as ctrl-A to vim via vims command mode, so I dont have to type ctrl-A? – alpha_989 May 25 '18 at 17:37
  • @alpha_989 Well, according to digitalronin.github.io/2016/06/28/vim-increment-column.html this guy types Ctrl-a a. Works on my end as well :) – Sergiy Kolodyazhnyy May 25 '18 at 17:53
  • @alpha_989 I've added another , command-mode solution. That avoids typing Ctrl+a altogether – Sergiy Kolodyazhnyy May 25 '18 at 18:11
13

You can always add your new entry with a x. newentry syntax and renumber everything afterwards with something like:

awk -F . -v OFS=. '{if (NF) $1 = ++n; else n = 0; print}'
  • -F .: sets the field separator to .1
  • -v OFS=.: same for the output field separator (-F . is short for -v FS=.).
  • {...}: no condition so the code inside {...} is run for each line
  • if (NF), if the number of fields is greater than 0. With FS being ., that means if the current line contains at least one .. We could also make it if (length) to check for non-empty lines.
  • $1 = ++n: set the first field to an incremented n (initially 0, then 1, then 2...).
  • else n = 0: else (when NF == 0) reset n to 0.
  • print: print the (possibly modified) line.

1The syntax is -F <extended-regular-expression> but when <extended-regular-expression> is a single character, that is not taken as a regular expression (where . means any character) but as that character instead.

  • this does the job for me :) I'd enjoy some explanation as I'm a beginner with awk – Zanna Aug 16 '16 at 16:18
  • @Zanna, see edit. – Stéphane Chazelas Aug 16 '16 at 17:50
6

Maximum overkill (and complexity! and bugs!) can be had via the Text::Autoformat perl module.

% < input                                                                    
1. foo
2. bar 100%
3. kittens
4. it is getting dark. there may be a grue
4. no seriously, it's getting dark
4. really, you should find a light or something.
4. are you even paying attention? helloooo
4. eat cake
5. unicorns
6. rainbows
% perl -MText::Autoformat -0777 -ple '$_=autoformat $_, { all => 1 }' < input
 1. foo
 2. bar 100%
 3. kittens
 4. it is getting dark. there may be a grue
 5. no seriously, it's getting dark
 6. really, you should find a light or something.
 7. are you even paying attention? helloooo
 8. eat cake
 9. unicorns
10. rainbows
% 

Actual results will depend on the input, desired output, options passed, etc.

3

Using basic shell tools, it can be done like this - it may even be simpler:

paste -d . <(seq $(wc -l < new.list)) <(cut -d . -f 2- < new.list) 

I'll explain from out- to inside:

paste -d . file1 file2 

creates output lines from the files as columns. -d . sets the separator to ., which will end up behind the new numbers.

commandA <(commandB)

starts commandB and presents the output as if it where a file to read from to commandA, which expects a filename as argument. (See command substitution.)

The second argument of paste, <(cut -d . -f 2- < new.list), outputs the second column unchanged, each line from the second field on, which means starting after the ..

The first argument of paste creates the new consecutive line numbers: it firsts counts the lines using "wordcount lines": $(wc -l < new.list), and uses this number to create a sequence of numbers from 1 to the count, with seq 7.

  • Oh, regarding the empty lines, that's right - I focused at the test case. But regarding the . - it assumes that there is the . after the number; Later . on a line have no effect, because then line will be cut at the first separator, before field 2, only (-f 2- meaning field 2 and following) - and does not touch further separators not used for cutting. It handles them as port of the field. Regarding the newline separated sections, csplit (also from coreutils) seems right; Roughly split at empty lines, apply abbove method, cat results together. Thanks for pointing it out! – Volker Siegel Aug 17 '16 at 0:26
  • @don_crissti I see - the OP's example is not clear about it - I looked at the example of @tring and got mislead by the consecutive 4., 4. 4. lines. Let's see... – Volker Siegel Aug 17 '16 at 0:35
  • +1 very nice for the first block (as in example)... I need to play with paste and seq some more... I can do it easily in an editor counting the lines, but I want a way that doesn't break if the number I want to insert is NOT the line number >_< – Zanna Aug 17 '16 at 5:47
  • 1
    If you use vim, you could run the command from vim on selected lines - if they are just few. (A script needs to handle that the input is read two times, using a tmp file or so.in a script) (I'm pretty sure it's possible without tmp file, but that uses really obscure shell features, maybe...) – Volker Siegel Aug 17 '16 at 8:49
  • 1
    It may be easier to do directly in vim, if you use that; The rectangular visual block selection would be useful for example: There is good advice on vim questions at vi.stackexchange.com, of course! – Volker Siegel Aug 17 '16 at 8:54
2

remove numbers: cut -d" " -f2- < list.txt > temp.txt

insert line to temp.txt

make numbers: cat -n temp.txt| sed -e 's/^[ ]*\([0-9]*\)[ \t]*\(.*\)/\1. \2/' > list.txt

  • @don_crissti why? – Ipor Sircer Aug 16 '16 at 15:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.