Insert a text before slash "/" character in bash scripts - regex

I have a line in a file as below:
2 14 * * * /run/opt/server/autoi.sh
And I want to insert "root" before /run/opt/server/autoi.sh as below:
2 14 * * * root /run/opt/server/autoi.sh
I have tried the following command
sed '//run/i root' filename
but it gives the following error:
sed: -e expression #1, char 0: no previous regular expression
Could you please help me to find a fix for it?

Use a different separator character, and you need to use the s command to substitute, e.g:
sed 's#/run#root /run#' filename
or without repeating /run:
sed 's#/run#root &#' filename

You are using the wrong sed command. The i command will insert a new line before the matching line, and of course, //run is not a valid regex at all.
The general form of a sed command is
<address> <action>
where address could be a regex or a line number, and action is a command.
In fact, you want an action without an address, which means it will be applied to every input line; and the action you want to perform is a substitution.
sed 's%/run%root &%' filename
We are using the & convenience shorthand to repeat the string which matched the first regex, and an alternate regex separator instead of / so that / does not itself get interpreted as a regex separator (equivalently, you could backslash-escape it, but here, that produces something called leaning toothpick syndrome).
This will print the results to standard output, not modify the file. Once you have verified that you get the results you want, you might want to add an -i option to modify the input file. (On some platforms, such as *BSD -- which includes MacOS -- you need -i '' with an empty argument.)

Simply substitute very first / with root / as follows.
sed 's/\//root \//' Input_file

sed -e 's/run/root\/run/' abc
Example:
[root#myIP tmp]# cat abc
2 14 * * * /run/opt/server/autoi.sh
[root#myIP tmp]# sed -e 's/run/root\/run/' abc
2 14 * * * /root/run/opt/server/autoi.sh
Edited: To add a username. this should do the trick
[root#myIP tmp]# sed -e 's/\//root \//' abc
2 14 * * * root /run/opt/server/autoi.sh

Related

How to use regex to find and replace a specific text?

I have the following code to replace the value between "-sav" and "test"
test="-val https://www.randomurl.com -sav 1aFAd381cCCb86FD300e7a3A399a6014.test -speed 2 -save -delay 14"
test=$(sed 's/-sav *.*\./-sav 12345./g' <<< $test)
echo $test
# -val https://www.randomurl.com -sav 12345.test -speed 2 -save -delay 14
How do I also include what is before -speed 2. Expected new variable value could be.
test="-val https://www.randomurl.com -sav 12345.itWorks -speed 2 -save -delay 14"
With your shown samples, please try following sed code. Have applied -E flag to enable ERE(extended regular expression) with sed program here and where test is OP's mentioned shell variable.
echo "$test" | sed -E 's/(^-.*sav )[^.]*\.[^ ]*(.*)/-sav (\1 12345.itworks\2)/g'
Explanation: Simple explanation would be, passing echo command's output as standard input to sed command. In sed program using regex (^-.*sav )[^.]*\.[^ ]*(.*), which creates 2 capturing groups in matching regex portion and then while substituting it using these 2 capturing groups(1st and 2nd one) along with newly required value 12355 as per OP's requirement.
NOTE: I am using g flag here with sed to perform substitution Globally in case you have only 1 match in your variable then remove it.

sed regex substitution with backreference why not working?

I am trying to get the complete filenames with extension (abc.jpg) using sed.
input.txt:
sfdb/asdjfj/abc.jpg
asdfj/asdfj/abd.gif
sfdb/asdjfj/abc.jpg
sfdb/asdjfj/abc-2.jpg
asdfjk/asdjf/asdf_?/sfdb/asdjfj/abc_12.jpg
asdfj/asdfj/abdasdfj
current command:
grep ".jpg" input.txt|sed 's:/\([^/]+\.jpg\):\1:gi'
the grep is just an example for getting the specified lines (although not necessary here). I already tested my regex to get only the last '/' + filename + .jpg: https://www.regex101.com/r/5GTgak/2
Expected Output:
abc.jpg
abc.jpg
abc-2.jpg
abc_12.jpg
But I am still getting the same input file. What am I doing wrong?
Use the following command:
sed -rn 's/^.*\/([^/]+\.jpg)$/\1/gp' input.txt
-r option allows extended regular expressions
/g - apply the replacement to all matches to the regexp, not just the first.
/p - if the substitution was made, then print the new pattern space.

process a delimited text file with sed

I have a ";" delimited file:
aa;;;;aa
rgg;;;;fdg
aff;sfg;;;fasg
sfaf;sdfas;;;
ASFGF;;;;fasg
QFA;DSGS;;DSFAG;fagf
I'd like to process it replacing the missing value with a \N .
The result should be:
aa;\N;\N;\N;aa
rgg;\N;\N;\N;fdg
aff;sfg;\N;\N;fasg
sfaf;sdfas;\N;\N;\N
ASFGF;\N;\N;\N;fasg
QFA;DSGS;\N;DSFAG;fagf
I'm trying to do it with a sed script:
sed "s/;\(;\)/;\\N\1/g" file1.txt >file2.txt
But what I get is
aa;\N;;\N;aa
rgg;\N;;\N;fdg
aff;sfg;\N;;fasg
sfaf;sdfas;\N;;
ASFGF;\N;;\N;fasg
QFA;DSGS;\N;DSFAG;fagf
You don't need to enclose the second semicolon in parentheses just to use it as \1 in the replacement string. You can use ; in the replacement string:
sed 's/;;/;\\N;/g'
As you noticed, when it finds a pair of semicolons it replaces it with the desired string then skips over it, not reading the second semicolon again and this makes it insert \N after every two semicolons.
A solution is to use positive lookaheads; the regex is /;(?=;)/ but sed doesn't support them.
But it's possible to solve the problem using sed in a simple manner: duplicate the search command; the first command replaces the odd appearances of ;; with ;\N, the second one takes care of the even appearances. The final result is the one you need.
The command is as simple as:
sed 's/;;/;\\N;/g;s/;;/;\\N;/g'
It duplicates the previous command and uses the ; between g and s to separe them. Alternatively you can use the -e command line option once for each search expression:
sed -e 's/;;/;\\N;/g' -e 's/;;/;\\N;/g'
Update:
The OP asks in a comment "What if my file have 100 columns?"
Let's try and see if it works:
$ echo "0;1;;2;;;3;;;;4;;;;;5;;;;;;6;;;;;;;" | sed 's/;;/;\\N;/g;s/;;/;\\N;/g'
0;1;\N;2;\N;\N;3;\N;\N;\N;4;\N;\N;\N;\N;5;\N;\N;\N;\N;\N;6;\N;\N;\N;\N;\N;\N;
Look, ma! It works!
:-)
Update #2
I ignored the fact that the question doesn't ask to replace ;; with something else but to replace the empty/missing values in a file that uses ; to separate the columns. Accordingly, my expression doesn't fix the missing value when it occurs at the beginning or at the end of the line.
As the OP kindly added in a comment, the complete sed command is:
sed 's/;;/;\\N;/g;s/;;/;\\N;/g;s/^;/\\N;/g;s/;$/;\\N/g'
or (for readability):
sed -e 's/;;/;\\N;/g;' -e 's/;;/;\\N;/g;' -e 's/^;/\\N;/g' -e 's/;$/;\\N/g'
The two additional steps replace ';' when they found it at beginning or at the end of line.
You can use this sed command with 2 s (substitute) commands:
sed 's/;;/;\\N;/g; s/;;/;\\N;/g;' file
aa;\N;\N;\N;aa
rgg;\N;\N;\N;fdg
aff;sfg;\N;\N;fasg
sfaf;sdfas;\N;\N;
ASFGF;\N;\N;\N;fasg
QFA;DSGS;\N;DSFAG;fagf
Or using lookarounds regex in a perl command:
perl -pe 's/(?<=;)(?=;)/\\N/g' file
aa;\N;\N;\N;aa
rgg;\N;\N;\N;fdg
aff;sfg;\N;\N;fasg
sfaf;sdfas;\N;\N;
ASFGF;\N;\N;\N;fasg
QFA;DSGS;\N;DSFAG;fagf
The main problem is that you can't use several times the same characters for a single replacement:
s/;;/..../g: The second ; can't be reused for the next match in a string like ;;;
If you want to do it with sed without to use a Perl-like regex mode, you can use a loop with the conditional command t:
sed ':a;s/;;/;\\N;/g;ta;' file
:a defines a label "a", ta go to this label only if something has been replaced.
For the ; at the end of the line (and to deal with eventual trailing whitespaces):
sed ':a;s/;;/;\\N;/g;ta; s/;[ \t\r]*$/;\\N/1' file
this awk one-liner will give you what you want:
awk -F';' -v OFS=';' '{for(i=1;i<=NF;i++)if($i=="")$i="\\N"}7' file
if you really want the line: sfaf;sdfas;\N;\N;\N , this line works for you:
awk -F';' -v OFS=';' '{for(i=1;i<=NF;i++)if($i=="")$i="\\N";sub(/;$/,";\\N")}7' file
sed 's/;/;\\N/g;s/;\\N\([^;]\)/;\1/g;s/;[[:blank:]]*$/;\\N/' YourFile
non recursive, onliner, posix compliant
Concept:
change all ;
put back unmatched one
add the special case of last ; with eventually space before the end of line
This might work for you (GNU sed):
sed -r ':;s/^(;)|(;);|(;)$/\2\3\\N\1\2/g;t' file
There are 4 senarios in which an empty field may occur: at the start of a record, between 2 field delimiters, an empty field following an empty field and at the end of a record. Alternation can be employed to cater for senarios 1,2 and 4 and senario 3 can be catered for by a second pass using a loop (:;...;t). Multiple senarios can be replaced in both passes using the g flag.

Regex in sed statement

I'm having some problems with the following expression from a bash script:
ipreg=^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$
sed -i 's/\"https:\/\/'$ipreg':9999\",/\"https:\/\/'${newip}':9999\",/g' /usr/share/config.file
Sed can't find the old Ip address with the var ipreg, but if i put in the exact address it does get replaced with the new IP.
I think you are making this too complex. I don't see a reason to match IPv4 addresses so tightly.
Why not simplify it a bit like this:
sed -i "s|https://[0-9.]*:9999|https://$newip:9999|" /usr/share/config.file
Also, you probably don't want anchors in your regex, since it's in the middle of a string. ^ matches the beginning of a line and $ matches the end - you don't want those in the middle of your match pattern.
Many sed dialects do not support {1,3} repetition. Try with backslashes before the curlies, and/or see if your sed has an -r or -E option to enable Extended Regular Expression (ERE) syntax.
In the worst case, just write the repetitions in longhand, or maybe simplify them to just * if you can live with the rather remote possibility for false positives.
As an aside, you should use quotes aroind anything which contains shell metacharacters.
This could work, taking into account the comment of tripleee.
$ cat config.file
.. / "https://10.10.10.10:9999";, /* #scratch /configuration/config.js/5 * * ==== default_route *
$ cat changeip.sh
#!/bin/bash
ipreg='[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'
newip='127.0.0.1'
sed -ri 's/"https:\/\/'"${ipreg}"':9999"/"https:\/\/'"${newip}"':9999"/' "$1"
cat "$1"
$ ./changeip.sh config.file
.. / "https://127.0.0.1:9999";, /* #scratch /configuration/config.js/5 * * ==== default_route *
Does the IP occur more than once? Only than you will need the global option.

unix sed command regular expression

Can anyone explain me how the regular expression works in the sed substitute command.
$ cat path.txt
/usr/kbos/bin:/usr/local/bin:/usr/jbin:/usr/bin:/usr/sas/bin
/usr/local/sbin:/sbin:/bin/:/usr/sbin:/usr/bin:/opt/omni/bin:
/opt/omni/lbin:/opt/omni/sbin:/root/bin
$ sed 's/\(\/[^:]*\).**/\1/g' path.txt
/usr/kbos/bin
/usr/local/sbin
/opt/omni/lbin
From the above sed command they used back reference and save operator concept.
Can anyone explain me how the regular expression especially /[^:]* work in the substitute command to get only the first path in each line.
I think you wrote an extra asterisk * in your sed code, so it should be like this:
$ sed 's/\(\/[^:]*\).*/\1/g' file
/usr/kbos/bin
/usr/local/sbin
/opt/omni/lbin
To change the delimiter will help to understand it a little bit better:
sed 's#\(/[^:]*\).*#\1#g'
The s#something#otherthing#g is a basic sed command that looks for something and changes it for otherthing all over the file.
If you do s#(something)#\1#g then you "save" that something and then you can print it back with \1.
Hence, what it is doing is to get a pattern like /[^:]* and then print is back. /[^:]* means / and then every char except :. So it will get / + all the string until it finds a semicolon :. It will store that piece of the string and then print it back.
Small examples:
# get every char
$ echo "hello123bye" | sed 's#\([a-z]*\).*#\1#g'
hello
# get everything until it finds the number 3
$ echo "hello123bye" | sed 's#\([^3]*\).*#\1#g'
hello12
[^:]*
in regex would match all characters except for :, so it would match until this:
/usr/kbos/bin
also it would match these,
/usr/local/bin
/usr/jbin
/usr/bin
/usr/sas/bin
As, these all contains characters, that are not :
.* match any character, zero or more times.
Thus, this regex [^:]*.*, would match all this expressions:
/usr/kbos/bin:/usr/local/bin:/usr/jbin:/usr/bin:/usr/sas/bin
/usr/local/bin:/usr/jbin:/usr/bin:/usr/sas/bin
/usr/jbin:/usr/bin:/usr/sas/bin
/usr/bin:/usr/sas/bin
However, you get only the first field (ie,/usr/kbos/bin, by using back reference in sed), because, regular expression output the longest possible match found.