Mutiline delete in FreeBSD - sed

How can we make this work in FreeBSD?
Multiple line delete block containing a pattern in FreeBSD.
sed '/{START-TAG/{:a;N;/END-TAG}/!ba};/ID: 222/d' data.txt
See
sed multiline delete with pattern.

In FreeBSD sed, you can't separate commands using a semi-colon. However, you may use -e chained commands:
sed -e '/{START-TAG/{' -e :a -e N -e '/END-TAG}/!ba' -e '}' -e '/ID: 222/d' file > outputfile
To save the contents inline, use
sed -i '' -e '/{START-TAG/{' -e :a -e N -e '/END-TAG}/!ba' -e '}' -e '/ID: 222/d' file

Don't use sed for anything that involves multiple lines, just use awk for a robust, portable solution. Given the sample input from the question you referenced, if the blocks are always separated by blank lines:
$ awk -v RS= -v ORS='\n\n' '!/ID: 222/' file
{START-TAG
foo bar
ID: 111
foo bar
END-TAG}
{START-TAG
foo bar
ID: 333
foo bar
END-TAG}
otherwise:
$ awk '/{START-TAG/{f=1} f{rec=rec $0 ORS} /END-TAG}/{if (rec !~ /ID: 222/) print rec; rec=f=""}' file
{START-TAG
foo bar
ID: 111
foo bar
END-TAG}
{START-TAG
foo bar
ID: 333
foo bar
END-TAG}
Both of those scripts will work using any awk in any shell on every UNIX box.

Related

programatically replace first occurence of string with sed or gnu sed

I want to replace only the first occurence of version: * in a file.
So I have a working sed command that work with GNU sed (source):
sed -i '0,/\(.*"version"\): "\(.*\)",/s//\1: '"\"${NEW_VERSION}\",/" package-lock.json
My problem is that i am executing this in scripts that also can run without GNU sed.
When i replace by sed -i '1,/\(.*"version"\): "\(.*\)",/s//\1: '"\"${NEW_VERSION}\",/" package-lock.json then it work without GNU sed but i have the following error when GNU sed is available:
sed: -e expression #1, char 0: no previous regular expression
EDIT: my main goal
As requested, here is my initial goal:
In a package.json and/or a package-lock.json , i want to replace the first occurence of version: X.X.X by version: Y.Y.Y where $NEW_VERSION containers Y.Y.Y
Using sed:
sed -i.bak -E '/(version: ).*/!{p;d;}
s//\1'"$NEW_VERSION"'/
:a
n
ba
' file
Alternatively this awk would also work:
awk -v ver="$NEW_VERSION" '!done && /^version:/{$2=ver; done=1} 1' file
You could check first occurrence by for example storing something in hold space.
sed '
# If hold space is empty
x;/^$/{x;
# If there is a pattern, replace it and..
/\("version": "\).*",/{
s//\1'"$NEW_VERSION"'"/1
# and hold the line.
h;
};x
};x
'
I'm going to simplify the expressions, since I'm not exactly sure what you're trying to match with the double quotes and the comma, and I think they obscure the main point. To replace just the first occurrence of foo with repl, you can do:
sed -e s/foo/repl/ -e ta -e p -e d -e :a -e n -e ba
The t command branches to the :a after a replacement is made, and the commands after :a just read and print each line without trying the substitution.
eg:
$ printf '%s\n' qux foo bar baz foo | sed -e s/foo/repl/ -e ta -e p -e d -e :a -e n -e ba
qux
repl
bar
baz
foo
But, this is really a lot easier with awk:
awk '/foo/ && !a{gsub("foo", "repl"); a = 1}1'

Is it possible to tell sed to perform the subsequent subsitutions if the first one fails?

I can pass a variable to sed substitution command as following:
$ myvar=helloworld
$ sed -r "s/$myvar/hellofoo/g; s/foo/bar/g" <(echo helloworld foo)
hellobar bar
But if the variable is empty, it fails:
$ myvar=
$ sed -r "s/$myvar/hellofoo/g; s/foo/bar/g" <(echo helloworld foo)
sed: -e expression #1, char 0: no previous regular expression
Is it possible to just skip the first substitution, and perform the second one? I'd like to have an output similar to this:
$ myvar=
$ sed -r "s/$myvar/hellofoo/g; s/foo/bar/g" <(echo helloworld foo)
helloworld bar
Since // reuses the last regular expression, you can prepend your sed program with an innocuous command that uses a regex that won't ever match anything:
$ myvar=
$ sed -r "/$^/ =; s/$myvar/hellofoo/g; s/foo/bar/g" <(echo helloworld foo)
helloworld bar
You could switch to awk
awk -v var="$myvar" '(var!=""){gsub(var,"hellofoo")}{gsub("foo","bar")}1' <(echo helloworld foo)

How to replace only last match in a line with sed?

With sed, I can replace the first match in a line using
sed 's/pattern/replacement/'
And all matches using
sed 's/pattern/replacement/g'
How do I replace only the last match, regardless of how many matches there are before it?
Copy pasting from something I've posted elsewhere:
$ # replacing last occurrence
$ # can also use sed -E 's/:([^:]*)$/-\1/'
$ echo 'foo:123:bar:baz' | sed -E 's/(.*):/\1-/'
foo:123:bar-baz
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):/\1-/'
456:foo:123:bar:789-baz
$ echo 'foo and bar and baz land good' | sed -E 's/(.*)and/\1XYZ/'
foo and bar and baz lXYZ good
$ # use word boundaries as necessary - GNU sed
$ echo 'foo and bar and baz land good' | sed -E 's/(.*)\band\b/\1XYZ/'
foo and bar XYZ baz land good
$ # replacing last but one
$ echo 'foo:123:bar:baz' | sed -E 's/(.*):(.*:)/\1-\2/'
foo:123-bar:baz
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):(.*:)/\1-\2/'
456:foo:123:bar-789:baz
$ # replacing last but two
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):((.*:){2})/\1-\2/'
456:foo:123-bar:789:baz
$ # replacing last but three
$ echo '456:foo:123:bar:789:baz' | sed -E 's/(.*):((.*:){3})/\1-\2/'
456:foo-123:bar:789:baz
Further Reading:
Buggy behavior if word boundaries is used inside a group with quanitifiers - for example: echo 'it line with it here sit too' | sed -E 's/with(.*\bit\b){2}/XYZ/' fails
Greedy vs. Reluctant vs. Possessive Quantifiers
Reference - What does this regex mean?
sed manual: Back-references and Subexpressions
This might work for you (GNU sed):
sed 's/\(.*\)pattern/\1replacement/' file
Use greed to swallow up the pattern space and then regexp engine will step back through the line and find the first match i.e. the last match.
A fun way to do this, is to use rev to reverse the characters of each line and write your sed replacement backwards.
rev input_file | sed 's/nrettap/tnemecalper/' | rev

Why does sed 'w' editing command truncate the file instead of appending to it?

I wrote this shell script.
echo a: foo > a.txt
echo a: bar >> a.txt
echo a: baz >> a.txt
sed -i -e '/bar/{ w b.txt' -e 'd }' a.txt
sed -i -e '/baz/{ w b.txt' -e 'd }' a.txt
cat b.txt
Here is the output I got.
a: baz
But I expected this output.
a: bar
a: baz
I expected this output because http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html says:
[2addr]w wfile
Append (write) the pattern space to wfile.
Since it says "append", I was assuming that the first sed command would append a: bar to a non-existent file, thus creating that file. Then the second sed command would append a: baz to the same file.
But the output above shows that this was not the case.
What is wrong in my understanding?
You run sed twice, and the first (and only) call to w each time truncates the file. To get your expected output, you need to run sed only once:
$ sed -i -e '/bar/{ w b.txt' -e 'd }' -e '/baz/{ w b.txt' -e 'd }' a.txt
$ cat b.txt
a: bar
a: baz
Or with less -e:
sed -i '/bar\|baz/{
w b.txt
d
}' a.txt
Alternation with \| is a GNU extension to basic regular expressions, just like -i is not part of POSIX sed either.
All write commands of a single sed invocation append to the same file. However, every new invocation truncates the file first.

How to find and replace all percent, plus, and pipe signs?

I have a document containing many percent, plus, and pipe signs. I want to replace them with a code, for use in TeX.
% becomes \textpercent.
+ becomes \textplus.
| becomes \textbar.
This is the code I am using, but it does not work:
sed -i "s/\%/\\\textpercent /g" ./file.txt
sed -i "s/|/\\\textbar /g" ./file.txt
sed -i "s/\+/\\\textplus /g" ./file.txt
How can I replace these symbols with this code?
Test script:
#!/bin/bash
cat << 'EOF' > testfile.txt
1+2+3=6
12 is 50% of 24
The pipe character '|' looks like a vertical line.
EOF
sed -i -r 's/%/\\textpercent /g;s/[+]/\\textplus /g;s/[|]/\\textbar /g' testfile.txt
cat testfile.txt
Output:
1\textplus 2\textplus 3=6
12 is 50\textpercent of 24
The pipe character '\textbar ' looks like a vertical line.
This was already suggested in a similar way by #tripleee, and I see no reason why it should not work. As you can see, my platform uses the very same version of GNU sed as yours. The only difference to #tripleee's version is that I use the extended regex mode, so I have to either escape the pipe and the plus or put it into a character class with [].
nawk '{sub(/%/,"\\textpercent");sub(/\+/,"\\textplus");sub(/\|/,"\\textpipe"); print}' file
Tested below:
> echo "% + |" | nawk '{sub(/%/,"\\textpercent");sub(/\+/,"\\textplus");sub(/\|/,"\\textpipe"); print}'
\textpercent \textplus \textpipe
Use single quotes:
$ cat in.txt
foo % bar
foo + bar
foo | bar
$ sed -e 's/%/\\textpercent /g' -e 's/\+/\\textplus /g' -e 's/|/\\textbar /g' < in.txt
foo \textpercent bar
foo \textplus bar
foo \textbar bar