How to change the character after specific exact character using SED command? - bash

I was writing a bash script to change all the B'10' values to B'12' in files.
So I have a file where B'10' is mentioned many times. And it can also be B'1010101010" with different length. All this has to be B'12121212". I tried to change with SED command which is :
sed -i -r "/[B'][10]+/s/10/12/g" filename
sed -i -r "/[B'][[0-9][0-9]]*[10]+/s/10/12/g" filename
I had to specify it twice, for only match B'10' and many B'1010101010..". If I only specified the second command, it was ignoring the single B'10' matches. So, This command is changing the values but it is changing for all the "10" matches it can find. But I need to change only after exactly B and single column near B character.
All the help is appreciated!! Thank you.

if your sed supports labels:
sed ':1 s/\(B\x27\(12\)*\)10/\112/; t1' file # or
sed -E ':1 s/(B\x27(12)*)10/\112/; t1' file
:1 label 1,
(B\x27(12)*) matches B' followed by zero or more 12s, puts it into capturing group 1,
\1 expands to value kept in capturing group 1,
t1 means "if a successful substitution is performed, go back to label 1".

try it by gnu sed before using -i option;
sed -E ":s s/\b(B')((12)*)10(10|\"|')/\1\212\4/ ;ts" filename

When you want to change all 10's after B', start with the last 10 in 1010...10.
After changing the last, do it again and replace the new last 10.
echo "B'1010101010111213" | sed -r ":a; s/B'((10)*)(10)/B'\112/; ta"

Related

How using sed one can find and replace a pattern with multiple strings?

I got this x.xx.xxx.xxxx.api-6.8.25-SNAPSHOT.jar filename, which I would like to change to, x.xx.xxx.xxxx.api_6.8.25.SNAPSHOT.jar. using sed I came up with this:
FILENAME=$(sed 's/-(?=[\w])/_/g' <<< "$FILENAME")
The regex pattern seems to be correct in pointing -s, however when my script runs no change is applied on my string. what I'm missing here? and how can I have multiple substitutions? changing the first dash with an underscore and the second with a dot?
I suggest:
echo 'x.xx.xxx.xxxx.api-6.8.25-SNAPSHOT.jar' | sed 's/-/_/; s/-/./'
Output:
x.xx.xxx.xxxx.api_6.8.25.SNAPSHOT.jar
Pure bash solution without calling any external utility:
fn='xx.xxx.xxxx.api-6.8.25-SNAPSHOT.jar`
fn="${fn/-/_}" # replace first - by _
fn="${fn/-/.}" # replace next - by .
echo "$fn"
xx.xxx.xxxx.api_6.8.25.SNAPSHOT.jar
You can use
FILENAME=$(sed -E 's/(.*)-([0-9.]+)-/\1_\2./' <<< "$FILENAME")
See the online demo.
Details:
-E enables POSIX ERE syntax
(.*)-([0-9.]+)- - a regex that matches and captures into Group 1 any zero or more chars, then -, then one or more digits or dots captured into Group 2 and then a -
\1_\2. is the replacement, Group 1, _, Group 2 and a ..

Find two string in same line and then replace using sed

I am doing a find and replace using sed in a bash script. I want to search each file for words with files and no. If both the words are present in the same line then replace red with green else do nothing
sed -i -e '/files|no s/red/green' $file
But I am unable to do so. I am not receiving any error and the file doesn't get updated.
What am I doing wrong here or what is the correct way of achieving my result
/files|no/ means to match lines with either files or no, it doesn't require both words on the same line.
To match the words in either order, use /files.*no|no.*files/.
sed -i -r -e '/files.*no|no.*files/s/red/green/' "$file"
Notice that you need another / at the end of the pattern, before s, and the s operation requires / at the end of the replacement.
And you need the -r option to make sed use extended regexp; otherwise you have to use \| instead of just |.
This might work for you (GNU sed):
sed '/files/{/no/s/red/green/}' file
or:
sed '/files/!b;/no/s/red/green/' file
This method allows for easy extension e.g. foo, bar and baz:
sed '/foo/!b;/bar/!b;/baz/!b;s/red/green/' file
or fee, fie, foe and fix:
sed '/fee/!b;/fi/!b;/foe/!b;/fix/!b;s/bacon/cereal/' file
An awk verison
awk '/files/ && /no/ {sub(/red/,"green")} 1' file
/files/ && /no/ files and no have to be on the same line, in any order
sub(/red/,"green") replace red with green. Use gsub(/red/,"green") if there are multiple red
1 always true, do the default action, print the line.

SED change second occurence in all lines

The normal expression to change server1 to server1-bck is
sed -i 's/server1/server1-bck/g' file.out
so all server1 will be changed to server1-bck. What I need is to change the second occurence of the expression in every line.
For example,
before text:
rename files tsm_node1 //server1/document/users/ //server1/document/users/
desired after text:
rename files tsm_node1 //server1/document/users/ //server1-bck/document/users/
How can I do that?
echo "rename files tsm_node1 //server1/document/users/ //server1/document/users/" |\
sed 's/server1/server1-bck/2g'
sed's famous substitution works like this:
sed 's/regex/replacement/flags'
Flags could be a number, in your case 2 for advising sed to execute this command on 2nd occurrence and if you need more, therefore the g flag is. If you are sure, there are no more items to be substituted, you can leave and forget the g flag.
If you don't pipe and have a file, do this:
sed -i 's/server1/server1-bck/2g' file.out.
Additionally you can replace parts of your regex pattern with sed's & replacement if you want to substitute with that what you have found and will have:
sed -i 's/server1/&-bck/2g' file.out.

Using BASH, how to increment a number that uniquely only occurs once in most lines of an HTML file?

The target is always going to be between two characters, 'E' and '/' and there will never be but one occurrence of this combination, e.g. 'E01/' in most lines in the HTML file and will always be between '01' and '90'.
So, I need to programmatically read the file and replace each occurrence of 'Enn/' where 'nn' in 'Enn/' will be between '01' and '90' and must maintain the '0' for numbers '01' to '09' in 'Enn/' while incrementing the existing number by 1 throughout the HTML file.
Is this doable and if so how best to go about it?
Edit: Target lines will be in one or the other formats:
<DT>ProgramName
<DT>Program Name
You can use sed inside BASH as a fantastic one-liner, either:
sed -ri 's/(.*E)([0-9]{2})(\/.*)/printf "\1%02u\3" $((10#\2+(10#\2>=90?0:1)))/ge' FILENAME
or if you are guaranteed the number is lower than 100:
sed -ri 's/(.*E)([0-9]{2})(\/.*)/printf "\1%02u\3" $((10#\2+1)))/ge' FILENAME
Basically, you'll be doing inplace search and replace. The above will not add anything after 90 (since you didn't specify the exact nature of the overflow condition). So E89/ -> E90/, E90/ -> E90/, and if by chance you have E91/, it will remain E91/. Add this line inside a loop for multiple files
A small explanation of the above command:
-r states that you'll be using a regular expression
-i states to write back to the same file (be careful with overwriting!)
s/search/replace/ge this is the regex command you'll be using
s/ states you'll be using a string search
(.E) first grouping of all characters upto the first E (case sensitive)
([0-9]{2}) second grouping of numbers 0 through 9, repeated twice (fixed width)
(/.) third grouping getting the escaped trailing slash and everything after that
/ (slash separator) denotes end of search pattern and beginning of replacement pattern
printf "format" var this is the expression used for each replacement
\1 place first grouping found here
%02u the replace format for the var
\3 place third grouping found here
$((expression)) BASH arithmetic expression to use in printf format
10#\2 force second grouping as a base 10 number
+(10#\2>=90?0:1) add 0 or 1 to the second grouping based on if it is >= 90 (as used in first command)
+1 add 1 to the second grouping (see second command)
/ge flags for global replacement and the replace parameter will be an expression
GNU sed and awk are very powerful tools to do this sort of thing.
You can use the following perl one-liner to increment the numbers while maintaining the ones with leading 0s.
perl -pe 's/E\K([0-9]+)/sprintf "%02d", 1+$1/e' file
$ cat file
<DT>ProgramName
<DT>Program Name
<DT>Program Name
<DT>Program Name
$ perl -pe 's/E\K([0-9]+)/sprintf "%02d", 1+$1/e' file
<DT>ProgramName
<DT>Program Name
<DT>Program Name
<DT>Program Name
You can add the -i option to make changes in-place. I would recommend creating backup before doing so.
Not as elegant as one line sed!
Break the commands used into multiple commands and you can debug your bash or grep or sed.
# find the number
# use -o to grep to just return pattern
# use head -n1 for safety to just get 1 number
n=$(grep -o "E[0-9][0-9]\/" file.html |grep -o "[0-9][0-9]"|head -n1)
#octal 08 and 09 are problem so need to do this
n1=10#$n
echo Debug n1=$n1 n=$n
n2=n1
# bash arithmetic done inside (( ))
# as ever with bash bracketing whitespace is needed
(( n2++ ))
echo debug n2=$n2
# use sed with -i -e for inline edit to replace number
sed -ie "s/E$n\//E$(printf '%02d' $n2)\//" file.html
grep "E[0-9][0-9]" file.html
awk might be better. Maybe could do it in one awk command also.
The sed one-liner in other answer is awesome :-)
This works in bash or sh.
http://unixhelp.ed.ac.uk/CGI/man-cgi?grep

How to apply two different sed commands on a line?

Q1:
I would like to edit a file containing a set of email ids such that all the domain names become generic.
Example,
peter#yahoo.com
peter#hotmail.co.in
philip#gmail.com
to
peter_yahoo#generic.com
peter_hotmail#generic.com
philip_gmail#generic.com
I used the following sed cmd to replace # with _
sed 's/#/_/' <filename>
Is there a way to append another sed cmd to the cmd mentioned above such that I can replace the last part of the domain names with #generic.com?
Q2:
so how do I approach this if I had text at the end of my domain names?
Example,
peter#yahoo.com,i am peter
peter#hotmail.co.in,i am also peter
To,
peter_yahoo.com#generic.com,i am peter
peter_hotmail.co.in#generic.com,i am also peter
I tried #(,) instead of #(.*)
it doesn't work and I cant think of any other solution
Q3:
Suppose if my example is like this,
peter#yahoo.com
peter#hotmail.co.in,i am peter
I want my result to be as follows,
peter_yahoo.com#generic.com
peter_hotmail.co.in#generic.com,i am peter,i am peter
How do i do this with a single sed cmd?
The following cmd would result in,
sed -r 's!#(.*)!_\1#generic.com!' FILE
peter_yahoo.com#generic.com
peter_hotmail.co.in,i am peter,i am peter#generic.com
And the following cmd wont work on "peter#yahoo.com",
sed -r 's!#(.*)(,.*)!_\1#generic.com!' FILE
Thanks!!
Golfing =)
$ cat FILE
Example,
peter#yahoo.com
peter#hotmail.co.in
philip#gmail.com
$ sed -r 's!#(.*)!_\1#generic.com!' FILE
Example,
peter_yahoo.com#generic.com
peter_hotmail.co.in#generic.com
philip_gmail.com#generic.com
In reply to user1428900, this is some explanations :
sed -r # sed in extended regex mode
s # substitution
! # my delimiter, pick up anything you want instead !part of regex
#(.*) # a literal "#" + capture of the rest of the line
! # middle delimiter
_\1#generic.com # an "_" + the captured group N°1 + "#generic.com"
! # end delimiter
FILE # file-name
Extended mode isn't really needed there, consider the same following snippet in BRE (basic regex) mode :
sed 's!#\(.*\)!_\1#generic.com!' FILE
Edit to fit your new needs :
$ cat FILE
Example,
peter#yahoo.com,I am peter
peter#hotmail.co.in
philip#gmail.com
$ sed -r 's!#(.*),.*!_\1#generic.com!' FILE
Example,
peter_yahoo.com#generic.com
peter#hotmail.co.in
philip#gmail.com
If you want only email lines, you can do something like that :
sed -r '/#/s!#(.*),.*!_\1#generic.com!' FILE
the /#/ part means to only works on the lines containing the character #
Edit2:
if you want to keep the end lines like your new comments said :
sed -r 's!#(.*)(,.*)!_\1#generic.com\2!' FILE
You can run multiple commands with:
sed -e cmd -e cmd
or
sed -e cmd;cmd
So, in your case you could do:
sed -e 's/#/_/' -e 's/_.*/_generic.com/' filename
but it seems easier to just do
sed 's/#.*/_generic.com/' filename
sed 's/\(.*\)#\(.*\)\..*/\1_\2#generic.com/'
Expression with escaped parentheses \(.*\) is used to remember portions of the regular expression. The "\1" is the first remembered pattern, and the "\2" is the second remembered pattern.
The expression \(.*\) before the # is used to remember beginning of the email id (peter, peter, philip).
The expression \(.*\)\. after the # is used to remember ending of the email id (yahoo, hotmail, gmail). In other words, it says: take something between # and .
The expression .* at the end is used to match all trailing symbols in the e-mail id (.com, .co.in, .co.in).

Resources