6

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
1
  • 2
    Are blocks contiguous, or is there a separator? (Even an empty line?) Commented Aug 16, 2016 at 15:54

5 Answers 5

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.

2
  • this does the job for me :) I'd enjoy some explanation as I'm a beginner with awk Commented Aug 16, 2016 at 16:18
  • @Zanna, see edit. Commented Aug 16, 2016 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.

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).'.'/ 
4
  • This is exactly the simple memorable answer that I was searching for and couldn't figure out! Commented Aug 28, 2016 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? Commented May 25, 2018 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 :) Commented May 25, 2018 at 17:53
  • @alpha_989 I've added another , command-mode solution. That avoids typing Ctrl+a altogether Commented May 25, 2018 at 18:11
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.

6
  • 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! Commented Aug 17, 2016 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... Commented Aug 17, 2016 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 >_< Commented Aug 17, 2016 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...) Commented Aug 17, 2016 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! Commented Aug 17, 2016 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

1
  • @don_crissti why? Commented Aug 16, 2016 at 15:43

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.