sed increment number
Asked Answered
G

4

7

I'd like to substitute some text with an incremental value. Considering file xx:

<outro>dfdfd</outro>
<RecordID>1</RecordID>
<outro>dfdfd</outro>
<RecordID>1</RecordID>
<outro>dfdfd</outro>
<RecordID>1</RecordID>
<outro>dfdfd</outro>

and sed command:

for n in seq 3;do sed -e 's/<RecordID>\d/<RecordID>'`echo $n`'/' xx; done

the echo $n command does not get incremented.

Tryed also:

n=1; sed -e 's/<RecordID>/<RecordID>'`echo $n ;let n=$n+1`'/g' xx

but with no success.

Considering only sed (no awk or perl) how can I have the RecordID field incremented as in:

<outro>dfdfd</outro>
<RecordID>1</RecordID>
<outro>dfdfd</outro>
<RecordID>2</RecordID>
<outro>dfdfd</outro>
<RecordID>3</RecordID>
<outro>dfdfd</outro>
Giana answered 16/12, 2010 at 16:25 Comment(3)
As you can see, this whole thing gets very messy with sed because you have to do the incrementing outside of it in a shell and then call one instance of sed for each line to process. If this is just for academic purposes, fine, but you really aught to be using awk for this because it can do math nativity. In fact the entire awk script is this one short line: awk '/RecordID/{sub("[0-9]+",++i)}1' infile > outfileBeckford
@SiegeX: I agree with your basic premise - sed is not the correct tool for the job. Suppose the record IDs are not presented in order in the file - the example shows them in order, but it is not clear that the numbers are necessarily monotonically increasing or even in sequence...how easily does 'awk' handle the capture and subsequent modification of part of the line? My suspicion is 'not very easily'; it is at that point that I head over to Perl (though I'd accept Python as a completely suitable alternative to Perl - I just happen to know Perl better than Python). Any views?Hypoderm
@Jonathan When you want to do something out of order with awk, it generally calls for use of associative arrays. And although it can be a bit awkward looking (no pun intended) I certainly would not put it in the "not very easily" category. I'm not quite sure how you want the O/P's input to change to show this, but here is an example I threw together not long ago that made thorough use of awk's associative arrays #4444083Beckford
R
8
while read line; do n=$((++n)) &&  echo $line|sed -e 's/[0-9]/'$(($n))'/' ; done < patt
Rider answered 16/12, 2010 at 17:2 Comment(5)
The second $(()) is unnecessary - just use $n.Carboniferous
while read line; do if [ "$(echo $line|grep Re)" ]; then n=$((++n)) && echo $line|sed -e 's/[0-9]/'$n'/' else : fi done < pattRider
The brackets, quotes and command substitution are unnecessary in the version in your comment: if echo "$line" | grep -qs ReCarboniferous
-q -s aren't portable. else part is unecessary.Rider
That's true. The portable equivalent is >/dev/null 2>&1 so it would look like this: if echo "$line" | grep -qs Re >/dev/null 2>&1; then ...Carboniferous
H
6

Notwithstanding the statement in the question that Perl and Awk cannot be used, the answers given so far demonstrate that sed is not the correct tool for this task. Both the answers I see only work with an extremely limited set of data and only work sanely on extremely small sets of data.

Using sed, there isn't a sensible method available to deal with thousands of records. You can hack solutions together, if you're careful, that deal with three record IDs (hint: you might need to map 3 to 4 before you map 2 to 3, lest the mapped 3 be remapped to 4).

Using Perl or Python, it is doable.

perl -e 's{<RecordID>(\d+)</RecordID>}{$n=$n+1; "<RecordID>$n</RecordID>"}e'

This is very much a case of 'use the right tool for the job'. sed is a great tool for the jobs for which it is designed (and a good many for which it was not designed). However, this is over-stressing it.

Hypoderm answered 16/12, 2010 at 17:16 Comment(3)
+1 for the right tool. Sometimes, however, you're faced with a nail that needs pounding and only have a screwdriver. Then what do you do? (I would drink the screwdriver and say "screw the nail". Then the pounding would be all in my head.)Carboniferous
how do you use this with a file?Titustityus
@OliverKuster: you specify the file name after the -e '...' part of the script. That will write the output to standard output. If you want the output to overwrite the original file, add an option such as -i.bak before the -e (but do test the changes first).Hypoderm
C
3

First, sed doesn't understand \d. Use [0-9] or [[:digit:]] instead.

Second, for n in seq 3 will set n to "seq" and "3". Use for n in $(seq 3) or (in Bash) use for n in {1..3}.

Third, you need to save the result of your replacement. If your version of sed supports -i (in-place), you can use that or you might need to use sed ... xx > xx.tmp && mv xx.tmp xx.

Fourth, you need to select which line to make the change to.

Put all that together and this works, but could be quite slow:

for n in $(seq 3); do sed -e $(($n*2))'s/<RecordID>[[:digit:]]/<RecordID>'$n'/' xx > xx.tmp && mv xx.tmp xx; done
Carboniferous answered 16/12, 2010 at 17:6 Comment(4)
The general form of Charles' answer is probably more efficient.Carboniferous
The first pass maps all of the ID values 0..9 to 2; the second pass maps all the 2's to 4; the third pass maps all the 4's to 6...doesn't it?Hypoderm
@Jonathan: No, because the $(($n*2)) selects which line to operate on.Carboniferous
Ahhh - OK, yes; highly specialized and extremely limited. sed is the wrong tool for this task.Hypoderm
C
1

The true way to do this with sed is of course to obviate the need for looping in the shell. Use something like Denis' address matcher to narrow down the scope, then use the expressions from the script in sed's info pages. It lexically increments numbers, no arithmetic involved. The sole trick it does is to care for the carry.

Chalfant answered 5/9, 2017 at 13:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.