Replace a line with sed but keeping original line - regex

I have a line like:
param1='123'
I would like the following:
param1='123'
param2=123
Where 123 can be any value.
I can get param2 using:
sed -i "s/param1=\([0-9]\+\)/param2='\1'/g" '{}' \;
But then I will lose param1.
I can also append line param2 using:
sed -i "param1='\([0-9]\+\)';/a \param2=\1;"
But the pattern isn't recognised and I end up with param2=1
Is there a way to combine these two commands or another way of working this?

Giving a extension to the -i flag create a backup so foo.ini will be updated and the original unmodified version will be found foo.ini.bak:
$ find . -name '*ini' -exec sed -ri.bak 's/param1=.([0-9]+)./&\nparam2=\1/' {} \;
In the sed command the g flag is probably redundant as in Unix configuration files a single option is set on a single line. The command replaces param1=123 with param1=123\nparam2=123 as & represents the whole match and the value 123 is caught in the first capture group.
& # Whole match
\n # Newline character
param2= # Literal string
\1 # First capture group
So basically the line is duplicated but option is changed and the value stays the same.

This might work for you (GNU sed):
sed "p;s/1='\([^']*\)'.*/2=\1/" file

You could say:
sed -r "s/(param1='([^']*)')/\1\nparam2=\2/" filename
(Add the -i option for in-place edit.)

sed -i "s/\(param1='\([0-9]\+\)'\)/\1\nparam2=\2/g"
seems to work.
Similar to what devnull answered, but works without the -r (Need to add backslash before parenthesis when not using extended regexp).

Related

Sed command to find the line containing a string and replacing part of it

I have a file, which should have an entry like this -
'new_revision': '111c1110b08b96c987482e08d28d84ea3d777egh',
I want to find this line and replace it with something like this
'new_revision': '000c0000b08b96c987482e08d28d84ea3d777eml',
I tried commands like-
sed -i 's/'new_revision': ''([a-z0-9]{40})''/'new_revision': '000c0000b08b96c987482e08d28d84ea3d777eml'/' file
But doesn't work properly and gives error as no matches found.
You could use -E and wrap the sed command in double quotes and use the capture group in the first part, using a backreference \1 in the replacement.
sed -E s/"('new_revision': ')[a-z0-9]{40}'/\1000c0000b08b96c987482e08d28d84ea3d777eml'/" file
Output
'new_revision': '000c0000b08b96c987482e08d28d84ea3d777eml',
You don't want to do the search and replace on every line, you only want to do it on the lines that match. In other words, you should restrict the lines on which the s command is run with an address. eg:
$ new_hash=000c0000b08b96c987482e08d28d84ea3d777eml
$ sed "/^'new_revision':/s/'[^']*',$/'$new_hash'/" input
awk -v tag="'new_revision':" -v val="'000c0000b08b96c987482e08d28d84ea3d777eml'," '$1==tag{$2=val} 1' file
'new_revision': '000c0000b08b96c987482e08d28d84ea3d777eml',

Replace a > with a " sed regular expression

I have lots of files that have lines that are in the following way:
#include "3rd-party/*lots folders*>
problem is that it ends with > instead of "
Is there a quick regex for sed to change that?
basically, if the line starts with #include "3rd-party, it should replace the last character to ".
Thanks in advance
You can use this:
sed -i '' '/^[[:blank:]]*#include "3rd-party/s/>$/"/' file
#include "3rd-party/*lots folders*"
Basically you can use:
sed '/^[[:space:]]*#include "3rd-party/s/>[[:space:]]*$/"/' file
Explanation:
/^[[:space:]]*#include/ is an address, a regular expression address. The subsequent command will apply to lines which start which optional space followed by an #include statement.
s/>[[:space:]]*$/"/ replaces > followed by optional space and the end of the line by a ".
Use the -i option if you want to change the file in place:
sed -i '/^[[:space:]]*#include/s/>[[:space:]]*$/"/' file
On a bunch of, let's say C files, use find and it's -exec option:
find . -name '*.c' -exec sed -i '/^[[:space:]]*#include/s/[[:space:]]*$/"/' {} \;
You can use sed for searching a pattern and doing an action on this line like
sed '/search_pattern/{action}' your_file
The action you want to do is replacing the last character in a line with >$ where > is your desired character and $ means that the searched character must be placed at the end of a line.
The action for doing this is the sedcommand s/// which work's like s/search_pattern/replace_pattern/.
This looks for your goal like:
sed '/#include "3rd-party/{s/>$/"/}' your_file
But since sed is a (s)tream (ed)itor you have to use sed's command flag -i to make your changes inline or pipe it with > to a new file.
Like this
sed -i '/#include "3rd-party/{s/>$/"/}' your_file
or like this
sed '/#include "3rd-party/{s/>$/"/}' your_file > new_file
Please let me know if this does your work.

Selective find/replace with sed

I need to do some find and replace in C++ source code: replace all occurrences of _uvw with xyz except when _uvw is part of abc_uvw or def_uvw. For example:
abc_uvw ghi_uvw;
jkl_uvw def_uvw;
should become:
abc_uvw ghixyz;
jklxyz def_uvw;
So far I came up with the following:
find . -type f -print0 | xargs -0 sed -i '/abc_uvw/\!s/_uvw/xyz/g'
This will replace all _uvw with xyz only in the lines that don't contain abc_uvw, which (1) doesn't handle such a case: abc_uvw ghi_uvw; and (2) doesn't take into account the second exception, that is def_uvw.
So how would one do that sort of selective find and replace with sed?
This might work for you (GNU sed):
sed -r 's/(abc|def)_uvw/\1\n_uvw/g;s/([^\n])_uvw/\1xyz/g;s/\n//g' file
Insert a newline infront of the strings you do not want to change. Change those strings which do not have a newline infront of them. Delete any newlines.
N.B. Newline is chosen as it cannot exist in an unadulterated sed buffer.
How about this?
$ cat file
abc_uvw ghi_uvw;
jkl_uvw def_uvw;
$ sed 's/abc_uvw/foo/g;s/def_uvw/bar/g;s/_uvw/xyz/g;s/foo/abc_uvw/g;s/bar/def_uvw/g' file
abc_uvw ghixyz;
jklxyz def_uvw;
You should use negative lookbehind. For example, in Perl:
perl -pe 's/(?<!(abc|def))_uvw/xyz/g' file.c
This performs a global substitution of any instances of _uvw that are not immediately preceded by abc or def.
Output:
abc_uvw ghixyz;
jklxyz def_uvw;
Sed is a useful tool and certainly has its place but Perl is a lot more powerful in terms of regular expressions. Using Perl, you get to specify exactly what you mean, rather than solving the problem in a more roundabout way.
This will work:
sed -e 's/abc_uvw/AAA_AAA/g; # shadow abc_uvw
s/def_uvw/DDD_DDD/g; # shadow def_uvw
s/_uvw/xyz/g; # substitute
s/AAA_AAA/abc_uvw/g; # recover abc_uvw
s/DDD_DDD/def_uvw/g # recover def_uvw
' input.cpp > output.cpp
cat output.cpp
sed 's/µ/µm/g;s/abc_uvw/µa/g;s/def_uvw/µd/g
s/_uvw/xyz/g
s/µd/def_uvw/g;s/µa/abc_uvw/g;s/µm/µ/g' YourFile
This is like the other in concept but "escaping" first the temporary pattern to filter on abc and def. I use µ but other char is possible, just avoid special sed char like /, \, &, ...

sed - Commenting a line matching a specific string AND that is not already commented out

I have the following test file
AAA
BBB
CCC
Using the following sed I can comment out the BBB line.
# sed -e '/BBB/s/^/#/g' -i file
I'd like to only comment out the line if it does not already has a # at the begining.
# sed -e '/^#/! /BBB/s/^/#/g' file
sed: -e expression #1, char 7: unknown command: `/'
Any ideas how I can achieve this?
Assuming you don't have any lines with multiple #s this would work:
sed -e '/BBB/ s/^#*/#/' -i file
Note: you don't need /g since you are doing at most one substitution per line.
Another solution with the & special character which references the whole matched portion of the pattern space. It's a bit simpler/cleaner than capturing and referencing a regexp group.
sed -i 's/^[^#]*BBB/#&/' file
I find this solution to work the best.
sed -i '/^[^#]/ s/\(^.*BBB.*$\)/#\ \1/' file
It doesn't matter how many "#" symbols there are, it will never add another one. If the pattern you're searching for does not include a "#" it will add it to the beginning of the line, and it will also add a trailing space.
If you don't want a trailing space
sed -i '/^[^#]/ s/\(^.*BBB.*$\)/#\1/' file
Assuming the BBB is at the beginning of a line, I ended up using an even simpler expression:
sed -e '/^BBB/s/^/#/' -i file
One more note for the future me. Do not overlook the -i. Because this won't work: sed -e "..." same_file > same_file.
sed -i '/![^#]/ s/\(^.*BBB.*$\)/#\ \1/' file
This doesn't work for me with the keyword *.sudo, no comments at all...
Ony the syntax below works:
sed -e '/sudo/ s/^#*/#/' file
Actually, you don't need the exclamation sign (!) as the caret symbol already negates whatever is inside the square brackets and will ignore all hash symbol from your search. This example worked for me:
sed -i '/[^#]/ s/\(^.*BBB.*$\)/#\ \1/' file
Comment all "BBB", if it's haven't comment yet.
sed -i '/BBB/s/^#\?/#/' file
If BBB is at the beginning of the line:
sed 's/^BBB/#&/' -i file
If BBB is in the middle of the line:
sed 's/^[^#]*BBB/#&/' -i file
I'd usually supply sed with -i.bak to backup the file prior to making changes to the original copy:
sed -i.bak '/BBB/ s/^#*/#/' file
This way when done, I have both file and file.bak and I can decide to delete file.bak only after I'm confident.
If you want to comment out not only exact matches for 'BBB' but also lines that have 'BBB' somewhere in the middle, you can go with following solution:
sed -E '/^([^#].*)?BBB/ s/^/#/'
This won't change any strings that are already commented out.

Using sed between specific lines only

I have this sed command for removing the spaces after commas.
sed -e 's/,\s\+/,/g' example.txt
How can i change it that, it will make the modification between only specific line numbers.
(e.g. between second and third lines).
Use:
sed '2,3s/,\s\+/,/g' example.txt
This will apply the regex /,\s\+/ only in the lines numbered 2 to 3 (inclusive) and substitute the match with ,.
Since OSX (BSD sed) has some syntax differences to linux (GNU) sed, thought I'd add the following from some hard-won notes of mine:
OSX (BSD) SED find/replace within (address) block (start and end point patterns(/../) or line #s) in same file (via & via & via & section 4.20 here):
Syntax:
$ sed '/start_pattern/,/end_pattern/ [operations]' [target filename]
Standard find/replace examples:
$ sed -i '' '2,3 s/,\s\+/,/g' example.txt
$ sed -i '' '/DOCTYPE/,/body/ s/,\s\+/,/g' example.txt
Find/replace example with complex operator and grouping (cannot operate without grouping syntax due to stream use of standard input). All statements in grouping must be on separate lines, or separated w/ semi-colons:
Complex Operator Example (will delete entire line containing a match):
$ sed -i '' '2,3 {/pattern/d;}' example.txt
Multi-file find + sed:
$ find ./ -type f -name '*.html' | xargs sed -i '' '/<head>/,/<\/head>/ {/pattern/d; /pattern2/d;}'
Hope this helps someone!
sed -e '2,3!b;s/,\s\+/,/g' example.txt
This version can be useful if you later want to add more commands to process the desired lines.