LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   ?? sed: how to use "1 s/" with -i to replace pattern only once? (https://www.linuxquestions.org/questions/programming-9/sed-how-to-use-1-s-with-i-to-replace-pattern-only-once-778793/)

GrapefruiTgirl 12-29-2009 08:54 PM

?? sed: how to use "1 s/" with -i to replace pattern only once?
 
I think I shouldn't need to be asking this, but I've been frigging around with this and reading sed tutorials for some time now, and I just can't get this to work.

In a text file, I have a bunch of identical lines, like this:


blah..

#STUB
#STUB
#STUB
#STUB
#STUB

more blah..

.. and I want to use sed -i to replace only the FIRST of those #STUB things, with a replacement.
It works in the console, without -i like so:

Code:

bash-3.1# echo "#RULESTUB
#RULESTUB
#RULESTUB
" | sed  "1 s/^#RULESTUB$/replacement/"

outputs:

replacement <--one replacement
#RULESTUB
#RULESTUB

but doing this from within a script, like so:
Code:

sed -i "1 s/^#STUB$/replacement/" $file
does not work. It doesn't replace anything. And if I remove the "1 " it replaces ALL the #STUB lines :confused:

What am I missing?

NOTE: if it matters, the sed operation is double quoted because in the real-world situation, 'replacement' is a $variable.

Sasha

GooseYArd 12-29-2009 09:05 PM

^ and $ are the beginning and end of line anchors. Your search pattern will only match a line that contains exactly "#STUB\n"

GrapefruiTgirl 12-29-2009 09:06 PM

That's *exactly* what the line(s) contains..

GooseYArd 12-29-2009 09:10 PM

in that case, double check your line endings and shell quoting (in case something is eating your end-of-line $), since I just pasted your example into a file here and it works alright.

GrapefruiTgirl 12-29-2009 09:12 PM

Oh!! You may have just got it -- update in a moment...

Nope.. I had thought maybe the $ was being interpreted literally, but it isn't..

FWIW, the replacement operation works fine without the "1 " but it replaces ALL the instances of #STUB, instead of quitting after one replacement. So the regex isn't the problem; in fact, I have an earlier line(s) in the script which use a similar regex, and it works fine (but that earlier line is SUPPOSED to replace ALL instances).

GooseYArd 12-29-2009 09:20 PM

do you mean the first instance in a line, or the first instance in the file?

GrapefruiTgirl 12-29-2009 09:22 PM

First instance in the file. I think I may have identified the problem though.. It may be PEBKAC :rolleyes:

Back in a moment.

GooseYArd 12-29-2009 09:24 PM

ah. make sure your quotes are escaped. I haven't got any idea about replace once per file.

GrapefruiTgirl 12-29-2009 09:28 PM

I'm relieved to NOT have to admit it was PEBKAC :)

But it isn't a quote-escape issue; I use double quotes often for stuff like this, when either the PATTERN or REPLACEMENT is a variable. But still, in this case, I'm stumped. It won't work...

Still trying though!

Thanks for the feedback so far.

Sasha

syg00 12-29-2009 09:38 PM

That "1" is not a match count, it is an address (line number). What's the first line of the file ?.

GrapefruiTgirl 12-29-2009 09:41 PM

@ syg00 -- Aha! So I *AM* doing this wrong after all.

You ask what's the first line of the file? You mean the line's contents? At this moment, the first line reads:

# # THE REAL ONE!

ghostdog74 12-29-2009 10:03 PM

awk
Code:

# more file
#STUB
#STUB
#STUB
#STUB
#STUB

$ awk '{s=(m>=1)?"#STUB":"new";}{m=m+sub("#STUB",s)}1' file
new
#STUB
#STUB
#STUB
#STUB

$ awk '/#STUB/ && !m{sub("#STUB","new");m+=1}m>=1' file
new
#STUB
#STUB
#STUB
#STUB


syg00 12-30-2009 12:31 AM

Conceptually I prefer the latter, however in the case of the target line not being the first line, data will be lost. Slight modification perhaps ?
Code:

awk '/#STUB/ && !m{sub("#STUB","new");m+=1}1' file

ghostdog74 12-30-2009 12:57 AM

Quote:

Originally Posted by syg00 (Post 3808551)
Conceptually I prefer the latter, however in the case of the target line not being the first line, data will be lost.

please give examples, so that i can do the modifications, since the solutions are all done with OP's sample.

syg00 12-30-2009 01:10 AM

As per post #7 from the OP:
Code:

# # THE REAL ONE!
#SLUB
#SLUB
#SLUB
...

Nothing to suggest there isn't more data lines before the required test data.

ghostdog74 12-30-2009 01:50 AM

sorry, so what should be the output of your example? i don't understand post #7, all i did understand is OP want to change only the first instance of "STUB". pls correct me again if i am wrong.
Code:

$ awk '/#STUB/ && !m{sub("#STUB","new");m+=1}1' file
# # THE REAL ONE!
new
#STUB
#STUB


syg00 12-30-2009 02:53 AM

True - but that is my adjustment to your code, not your original offering ...

konsolebox 12-30-2009 06:14 AM

to change only the first match, you have to add the quit command like

Code:

sed -i '/pattern/{ s/.../.../; q; }' file

syg00 12-30-2009 06:27 AM

Hmmm - did you test that ?.
I'm a big fan of sed, but use the right tool for the job. Personally I'd probably use perl, but I'm happy to continue to get my meagre awk knowledge improved by ghostdog74 and a couple of others on this list.

GrapefruiTgirl 12-30-2009 10:07 AM

OK, first, thanks for the continued input everyone! I took a leave last night when Star Wars came on the TV at midnight..

Personally, I feel that sed *is* the right tool for this job, but I just haven't got it quite right yet :scratch: and, of course, maybe AWK is better for this, and I just don't know it yet. Given what sed can do, I just don't understand why it won't do this properly. So, I have not *yet* tried the AWK ideas here, because I want to use sed and/or rule it out as an option.

For the record/clarification, here's a better representation of what the target file looks like:

Code:

data...

data...
...
data...

...
...
data...
#PATTERN
#PATTERN
#PATTERN
data...
data...
...
#EOF

Konsolebox has got me the closest using sed:

Code:

sed -i "/#PATTERN$/ {s/^\#PATTERN$/$replacement/; q;}" $file
trouble with that is, it truncates the file after matching the pattern and doing the operation, leaving the edited line as the LAST line in the target file. No good.

At least I have learned one way to use the 'q' option. Maybe moving the 'q' around will help.. Still at it.

Sasha

osor 12-30-2009 11:37 AM

Quote:

Originally Posted by GrapefruiTgirl (Post 3808952)
Personally, I feel that sed *is* the right tool for this job,

I disagree.
Code:

sed -e "/^#PATTERN$/{x;s/././;x;t;s/^#PATTERN$/$replacement/;h}" data
Much less elegant than awk.

David the H. 12-30-2009 12:31 PM

How do I match only the first occurrence of a pattern?

I too think sed is the better tool, as it's a just a simple line match and substitution. You just need to know how to do it right.

Code:

sed '0,/^#STUB$/ s//#FOO/'

GrapefruiTgirl 12-30-2009 12:58 PM

@ David.The.H -- Thank you,

You know, I have that very link open already (and a few others too), yet for some reason, missed that part (or subconsciously neglected to pay attention to it :/).

I will be trying it as soon as I finish working on this OTHER part of the same program. Hopefully it will work, as it is simpler than awk too.

Meanwhile, I had come up with another solution, which I may now (probably will) toss in favor of this method.
What I came up with, was to simply have only ONE of the pattern line in the target file; so the sed operation would find that line, and replace it with the new data PLUS a newline PLUS a new pattern line, so that future operations would work.
Like so:

replace:
#STUB

with:
New data blah blah blah\n#STUB

It works great; only questionable thing with it is that, if for some reason Joe User decides to create multiple copies of the #STUB target line in his copy of the target file, then I'm half-way back to the start of this all; multiple replacements will get made.

So, by switching to the 0, method you gave, I will be assured the replacement will only happen once, regardless.

I'll update again later on this.

Thanks again to everyone who helped out here,

Sasha

syg00 12-30-2009 04:54 PM

The things you learn - didn't know about that: thank you @David the H. GNU sed has many niceties that aren't available everywhere - I have a *very* non-GNU sed requirement sometimes. Hence my desire for other solutions rather than resorting to figuring out ugly hacks as per @osor.

konsolebox 12-31-2009 02:22 AM

Quote:

Originally Posted by syg00 (Post 3808762)
Hmmm - did you test that ?.

I haven't. Sorry. Too bad it didn't work. It's just that I sort of remember using it multiple times. Maybe it works on one way streams and not with the '-i' option. I might have also added some alternations like '!' and 'b'.

Quote:

Originally Posted by syg00 (Post 3808762)
I'm a big fan of sed, but use the right tool for the job. Personally I'd probably use perl, but I'm happy to continue to get my meagre awk knowledge improved by ghostdog74 and a couple of others on this list.

Well everyone's got their ways. On my side, I also thought that it was just simple and that sed should really be the only right tool for the job as based on the original requirements. I think other tools will just appear as heavier. Just my taste though. I have the tendency to be critical with balancing speed, readability, etc. I'd prefer to use awk if it's just got a bit more complicated. For example, input text may also contain characters that can give conflict with sed's syntax.

GrapefruiTgirl 12-31-2009 02:33 AM

In this case, for the record, speed is a factor in considering which tool I use.
I have replaced dozens of awk operations in this program with `sed` or `tr` equivalents, because my testing showed that sed is usually faster than awk when doing relatively simple text substitutions, and tr is probably faster than both, but the difference was immeasurable in my testing.

I still have not yet tried the 0, method with sed in this particular scenario; other work has been keeping me busy. But I will update when I do try it.

Sasha

syg00 12-31-2009 02:52 AM

FWIW, my testing on (really) big files showed perl faster than sed - most surprised was I.
Maybe if you can consolidate a lot of those calls into one (perl ???) you'll save even more in the "setup/teardown" overheads of lots of small calls.

konsolebox 01-01-2010 05:39 AM

Quote:

Originally Posted by syg00 (Post 3809681)
FWIW, my testing on (really) big files showed perl faster than sed - most surprised was I.
Maybe if you can consolidate a lot of those calls into one (perl ???) you'll save even more in the "setup/teardown" overheads of lots of small calls.

Truly reasonable if you're going to use sed multiple times inside a script. I wonder though what's quicker by average, perl or awk...? I think awk's pretty quick with text processing even with hashes (similarly associative arrays in bash). How 'bout in perl?... Maybe it depends on the implementation but awk already does the compilation of the code before executing.


All times are GMT -5. The time now is 02:54 AM.