How to stop at first occurrence while replacing a multiline text [duplicate] - sed

This question already has answers here:
How to use sed to replace only the first occurrence in a file?
(25 answers)
Closed 7 years ago.
I have a file pom.xml, which needs to have the version with a SNAPSHOT appended to it. At times, the SNAPSHOT is missing, which results in the build failing. I'm trying to replace it with SNAPSHOT if it doesn't exist.
Actual:
<artifactId>package1</artifactId>
<version>1.0.0</version>
Desired:
<artifactId>package1</artifactId>
<version>1.0.0-SNAPSHOT</version>
Since the artifactId is not going to be the same across all the projects, I tried to search for a pattern that starts from </artifactId> to </version> and replace it with the SNAPSHOT text added to it. This has resulted in replacing the text till the last version available in pom.xml.
I've tried the following:
cat pom.xml | sed ':a;N;$!ba;1,/<\/artifactId>/s/<\/artifactId><version>[0-9]*.[0-9]*.[0-9]*<\/version>/<\/artifactId><version>1.0.0-SNAPSHOT<\/version>/g'
(Not working at all as it's unable to find the end of line).
cat common-pom.xml | sed ':a;N;$!ba;0,/<\/artifactId>/s/<\/artifactId>.*<\/version>/<\/artifactId><version>0.0.0-SNAPSHOT<\/version>/g'
(Is able to do the changes, but it's replacing all the text till the last version available in pom.xml).
How do I stop the replacement to the first occurrence in a multi line replacement?

If I understood the question correctly, this should work:
sed -r '
/<artifactId>/ {
n
s/(([0-9]+\.){2}[0-9])(<\/version>)/\1-SNAPSHOT\3/
}
' infile
You read the complete file into sed before starting with replacements. This just looks for a line with <artifactId>, reads the next line (n) and makes the replacement there.
Also, instead of using cat to pipe to sed, you can just give the file to be processed as an argument to sed.
This works also without -r, but then all parentheses and braces would have to be escaped.
Example file:
<artifactId>package1</artifactId>
<version>1.0.0</version> <-- should get SNAPSHOT
<artifactId>package1</artifactId>
<version>1.0.0-SNAPSHOT</version> <-- already has SNAPSHOT
Somethingelse
<version>1.0.0</version> <-- doesn't follow artifactId line
results in
<artifactId>package1</artifactId>
<version>1.0.0-SNAPSHOT</version>
<artifactId>package1</artifactId>
<version>1.0.0-SNAPSHOT</version>
Somethingelse
<version>1.0.0</version>

Related

how to do sed in-line replacement without backup file if the original file is not changed ? in case the file was not changed

I would like to retain a backup file, only if sed altered the original file.
for example:
I have the following file:
# cat test
This is example file
abcd
efgh
process with sed so there is nothing to change:
# sed -i.BAK "s/AAAA/BBBB/" test
The "test" file is not changed because nothing matched. In this case, I would like to avoid the backup file that was created:
# md5sum test*
d3ca57583595576338ad6f9a01276cd5 test
d3ca57583595576338ad6f9a01276cd5 test.BAK
I learned that what I was asking is not possible by "sed" as I suspected by RTFM.
I solved by adding "if [ grep ... ] " on the expression needed to replace.
The "sed" is performed if and only if the expression exists.
Thanks for the people that commented.

How to insert multi lines to end of file using sed

i want to inset multi lines befor last line in the file
that using sed , used the following sed attr
> sed -i '/\/web-app/r web.xml' inputerror.txt
last line in the file
</web-app>
inputerror.txt content as following
<error-page>
<error-code>404</error-code>
<location>/web/404</location>
</error-page>
nothing added to my web.xml any advise here
The following works in GNU sed:
sed '$s#^#<error-page>\n <error-code>404</error-code>\n <location>/web/404</location>\n</error-page>\n#' web.xml
But I don't know how portable \n is or how to make it read from a file.

sed add line if not exists

I need to make a change in the php.ini configuration file via sed (or similar).
I need to add the following text:
extension=solr.so
The line has to be added as line number 941 in the configuration file. However, if the file is already there, it should not be added again.
I guess there are two approaches: 1) replace line 941 with the text, or 2) search for the text and add it to line 941 if there are not matches.
I have the following command that works fine, except that the line is added again if the script is run again:
sed '941i\
extension=solr.so' /etc/php5/apache2/php.ini > /etc/php5/apache2/php.ini
How can I make sure that this command does not add the line if it is already there?
The easiest way would be to test before using grep, for example:
grep -q -e 'extension=solr.so' file || sed '...'
Also, it is estrange that you need exactly that line. You should add it at the end, or something like that.
Also, note that taking the same file as input and output never should be done. This can damage the file badly. You should be using the -i sed parameter to do in-place editing.

Nant, SqlCmd -v switch and spaces in nant property fails build with invalid argument

I have a nant script that ...
1. takes the content of disc-file
2. assigns that content to a nant property
3. and then calls sqlcmd with a -v passing in that property containg the content of the disc file
4. inside the sql script the contents of the file should be used by a stored proc.
The problem is that when the content of the file contains a space the nant build stops with a "Invalid argument" issue
Anone know a way around this ?
The top part of the nant script is ...
<?xml version="1.0"?>
<!-- the main name of this project -->
<project name="Hops" default="all">
<!-- BuildHistory -->
<property name="buildHistoryContents" value="" />
<xmlpeek xpath="/" file="BuildNotes.xml" property="buildHistoryContents"></xmlpeek>
<!-- <echo message="${buildHistoryContents}" /> -->
<!-- ***************** -->
<target name="ExecSql">
<echo message="running sql script : ${SqlBuildScriptsDir}${sqlBuildFileName}" />
<exec program="${SqlCmd}" commandline="-S ${SqlServerInstanceName} -E -d HBus -i ${SqlBuildScriptsDir}${sqlBuildFileName} -v vSchemaVersion=${buildHistoryContents} " />
</target>
The sql script contains the line ...
exec lsp_SchemaVersionUpsert '1.4', N'$(vSchemaVersion)'
A disc file content that works is ...
<BuildNotes>
<Note>
<buildVer>HasNotSpace</buildVer>
</Note>
</BuildNotes>
A disc file content that does not works is ...
<BuildNotes>
<Note>
<buildVer>Has Space</buildVer>
</Note>
</BuildNotes>
The use of all this is pass xml build comments to a table logging version build history for the db schema.
Does anyone know an alternate method or know a way through this ?
The next part, added after Phillip Keeley correcty solved first part (the SPACE Problem)
I simplified the original task to simplify the question.
There is also a Quoted Attribute Problem ; xml quoted attributes cause the nant build to fail with "Invalid Argument".
eg this will cause nant to choke but removing the dt attribute will enable the nant build to succeed ...
<BuildNotes>
<Note>
<buildVer>1.4</buildVer>
<dateStarted>09/24/2009 11:25:42</dateStarted>
<Item dt="20091008" >SpacesAndNoQuotedAttribute</Item>
</Note>
</BuildNotes>
Any ideas ... ?
Your problem is (of course) in line
<exec program="${SqlCmd}" commandline="-S ${SqlServerInstanceName} -E -d HBus -i ${SqlBuildScriptsDir}${sqlBuildFileName} -v vSchemaVersion=${buildHistoryContents} " />
specifically, in
-v vSchemaVersion=${buildHistoryContents}
The NAnt expression replaces property ${buildHistoryContents} with the stored value--which will include any embedded spaces. Problem is, when calling SQLCMD (I"m assuming that's what ${SqlCmd} resolves to) from a command window, the values for any and all -v parameters are space delimited -- that is, the parser hits -v, reads the next characters through the "=" as the variable name, then reads all characters after the = and through the next space (or end of line) as the value to assign to the variable, and that embedded space will mess you up bigtime.
On the command line, the work-around is to wrap the variable value in quotes:
- v MyVariable=Hello World
becomes
- v MyVariable="Hello World"
That doesn't work here, because it's XML and you have to wrap the commandline attribute of the exec element with quotes... and embedded quotes will, once again, mess you up bigtime.
I believe the work-around here is to use XML macro substitution (I quite possibly have the formal titles of these concepts wrong) for those embedded quotes. This value should be
"
Which means that the following should work:
<exec program="${SqlCmd}" commandline="-S ${SqlServerInstanceName} -E -d HBus -i ${SqlBuildScriptsDir}${sqlBuildFileName} -v vSchemaVersion="${buildHistoryContents}" " />
Please try this and see -- I may have to do something like this myself some day soon.

Unable to use SED to edit files fast

The file is initially
$cat so/app.yaml
application: SO
...
I run the following command. I get an empty file.
$sed s/SO/so/ so/app.yaml > so/app.yaml
$cat so/app.yaml
$
How can you use SED to edit the file and not giving me an empty file?
$ sed -i -e's/SO/so/' so/app.yaml
The -i means in-place.
The > used in piping will open the output file when the pipes are all set up, i.e. before command execution. Thus, the input file is truncated prior to sed executing. This is a problem with all shell redirection, not just with sed.
Sheldon Young's answer shows how to use in-place editing.
You are using the wrong tool for the job. sed is a stream editor (that's why it's called sed), so it's for in-flight editing of streams in a pipe. ed OTOH is a file editor, which can do everything sed can do, except it works on files instead of streams. (Actually, it's the other way round: ed is the original utility and sed is a clone that avoids having to create temporary files for streams.)
ed works very much like sed (because sed is just a clone), but with one important difference: you can move around in files, but you can't move around in streams. So, all commands in ed take an address parameter that tells ed, where in the file to apply the command. In your case, you want to apply the command everywhere in the file, so the address parameter is just , because a,b means "from line a to line b" and the default for a is 1 (beginning-of-file) and the default for b is $ (end-of-file), so leaving them both out means "from beginning-of-file to end-of-file". Then comes the s (for substitute) and the rest looks much like sed.
So, your sed command s/SO/so/ turns into the ed command ,s/SO/so/.
And, again because ed is a file editor, and more precisely, an interactive file editor, we also need to write (w) the file and quit (q) the editor.
This is how it looks in its entirety:
ed -- so/app.yaml <<-HERE
,s/SO/so/
w
q
HERE
See also my answer to a similar question.
What happens in your case, is that executing a pipeline is a two-stage process: first construct the pipeline, then run it. > means "open the file, truncate it, and connect it to filedescriptor 1 (stdout)". Only then is the pipe actually run, i.e. sed is executed, but at this time, the file has already been truncated.
Some versions of sed also have a -i parameter for in-place editing of files, that makes sed behave a little more like ed, but using that is not advisable: first of all, it doesn't support all the features of ed, but more importantly, it is a non-standardized proprietary extension of GNU sed that doesn't work on many non-GNU systems. It's been a while since I used a non-GNU system, but last I used one, neither Solaris nor OpenBSD nor HP-UX nor IBM AIX sed supported the -i parameter.
I believe that redirecting output into the same file you are editing is causing your problem.
You need redirect standard output to some temporary file and when sed is done overwrite the original file by the temporary one.