I am trying to change lines like this
print("hc-takescreenshot: 'this is the description','1'")
print("hc-takescreenshot: 'this is another description','2'")
print("hc-takescreenshot: 'this is yet another description','3'")
to this
doscreenshot("This is the homepage", "1", self)
doscreenshot("This is another homepage", "2", self)
doscreenshot("This is yet another homepage", "3", self)
I can do something like this
find='print(\"hc-takescreenshot: \x27'
replace='doscreenshot("'
sed -i -e "s/$find/$replace/g" $newpy
But that only deals with the first part, I then need to replace the last bit, for example: ,'3'")
What I need to be able to do is extend the replace when the the match is made.
Assuming that you want to keep the This is... description strings(it's not clear reading your question), you can try this:
sed -i -e "s/.*: '\([^']*\)','\([0-9]*\)'.*/doscreenshot(\"\1\", \"\2\", self)/" "$newpy"
At first sight, there doesn't seem to be a method for determining what to match and what to replace it with, so you may as well use three distinct -e 's/…/…/' argument pairs in a single invocation of sed:
newpy=data
find='print(\"hc-takescreenshot: '\'
replace='doscreenshot(' # Dropped "
sed -e "s/$find.*1'\")/$replace\"This is the homepage\", \"1\", self)/" \
-e "s/$find.*2'\")/$replace\"This is another homepage\", \"2\", self)/" \
-e "s/$find.*3'\")/$replace\"This is yet another homepage\", \"3\", self)/" \
"$newpy"
The \x27 used in the question didn't work on Mac (with Bash 3); I used \' to add a single quote to the end of find instead.
However, more intense scrutiny shows that (as well as the function call change) you might want to convert this to This and description' to homepage" and the original single-quoted number to a double-quoted number followed by , self. If that's accurate, then you could do that with:
find='print(\"hc-takescreenshot: '\'
replace='doscreenshot("' # Reinstated "
sed -e "/$find/ {" \
-e "s//$replace/" \
-e "s/this/This/" \
-e "s/description'/homepage\"/" \
-e "s/'\([0-9]\)'\"/ \"\1\", self/" \
-e "}" \
"$newpy"
The braces group the commands in between so that only lines that match the $find are operated on by the 4 substitution commands. That simplifies the matching. The s//$replace/ uses sed's "reuse last regular expression" capability to avoid repeating the match string in the script.
Yes, you could slam all the script into a single -e argument if you really like unreadable code and won't be needing to do this more than once. However, as soon as you find you need to make more than one run, it is better to split things up so that it is readable. Another option would be to create a file (e.g. script.sed) containing the plain sed commands (without having to worry about the shell's handling of quotes):
/print("hc-takescreenshot: '/ {
s//doscreenshot("/
s/this/This/
s/description'/homepage"/
s/'\([0-9]\)'"/ "\1", self/
}
and you can then run:
sed -i.bak -f script.sed "$newpy"
For the 3 lines of input shown in the question, all three scripts generate the answer:
doscreenshot("This is the homepage", "1", self)
doscreenshot("This is another homepage", "2", self)
doscreenshot("This is yet another homepage", "3", self)
All code now tested on macOS Sierra 10.12.2 with BSD sed and GNU sed and Bash 3.2.57(1).
Related
I am trying to add a line in a file using sed after first match. I looked my threads online, addressing the same issue and tried below versions of the command:
sudo sed -e '/cfg_file/cfg_file='$NEW_FILE -e '/cfg_file/cfg_file=/q' MAIN_CONF_FILE
sudo sed -i '0,/cfg_file/ a\cfg_file='$NEW_FILE $MAIN_CONF_FILE
sudo sed -i '1s/cfg_file/cfg_file='$NEW_FILE $MAIN_CONF_FILE
sudo sed -i '1/cfg_file/s/cfg_file='$NEW_FILE $MAIN_CONF_FILE
Unfortunately, nothing worked for me. Either they show error, in case of point 3, or show similar behavior of adding lines after each match.
SAMPLE FILE
cfg_file=some_line1
cfg_file=some_line2
Now I want to add a line after first match of cg_file, like below.
EXPECTED RESULT
cfg_file=some_line1
cfg_file=my_added_line_after_first_match_only.
cfg_file=some_line2
Help me in adding line after first match and correcting my command.
Since you're on Ubuntu, you are using GNU sed. GNU sed has some weird features and some useful ones; you should ignore the weird ones and use the useful ones.
In context, the useful one is ranges starting at line 0. Weird ones are the way it messes with a, i and c commands.
MAIN_CONF_FILE=/tmp/copy.of.main.config.file
NEWFILE="my_added_line_after_first_match_only."
sed -e '0,/^cfg_file=/ { /^cfg_file/ a\' \
-e "cfg_file=$NEWFILE" \
-e '}' \
"$MAIN_CONF_FILE"
In classic sed, the a command is followed by backslash-newline, and each subsequent line of the script is appended up to and including the first line without a backslash at the end (and the backslash is removed). Each -e argument functions as a line in the script. Distinguish between the shell lines escaped with backslash at the end and the sed script lines with backslash at the end.
Example at work
$ cat /tmp/copy.of.main.config.file | so
cfg_file=some_line1
cfg_file=some_line2
$ cat script.sh
MAIN_CONF_FILE=/tmp/copy.of.main.config.file
NEWFILE="my_added_line_after_first_match_only."
SED=/opt/gnu/bin/sed
${SED} \
-e '0,/^cfg_file=/ { /^cfg_file/ a\' \
-e "cfg_file=$NEWFILE" \
-e '}' \
"$MAIN_CONF_FILE"
$ bash script.sh
cfg_file=some_line1
cfg_file=my_added_line_after_first_match_only.
cfg_file=some_line2
$
This is based on your attempt 2, but avoids some of the weird stuff.
Basic sanity
As I noted, it is not sensible to experiment with sudo and the -i option to sed. You don't use those until you know that the script will do the job correctly. It is dangerous to do anything as root via sudo. It is doubly dangerous when you don't know whether what you're trying to use will work. Don't risk wrecking your system.
This question already has an answer here:
sed fails with "unknown option to `s'" error [closed]
(1 answer)
Closed 7 years ago.
I'm trying to use sed to update a config file using a bash script. I have a similar sed command right above this one in the script that runs fine. I can't seem to figure out why this is breaking:
sed -i.bak \
-e s/"socketPath:'https://localhost:9091'"/"socketPath:'/socket'"/g \
$WEB_CONF
Any ideas?
The quotes and double quotes are causing problems.
You are using them in view of the slashes in the string.
In sed you can use another delimiter, such as a #.
sed -e 's#socketPath:https://localhost:9091#socketPath:/socket#g' \
$WEB_CONF
Escape your slashes in the pattern or use a different delimiter like this:
sed -i.bak \
-e s%"socketPath:'https://localhost:9091'"%"socketPath:'/socket'"%g \
$WEB_CONF
I am confused looking at the delimiters you have used so you can't blame sed for goofing up. When your dataset has / in them, it is often advised to use a different delimiters like # or _ (yes, sed supports various delimiters).
Also, your quoting looks abit off. The syntax should be:
sed -i.bak 's#substitute#replacement#g' "$WEB_CONF"
If your substitute and/or replacement has variables use " instead ' which will allow it to interpolate.
sed -e '/pattern/ {d;n}'
sed -e '/pattern/ {d}'
Are these two commands the same on Linux?
Is it meaningful to put n at the end?
From "man sed", I got :
n N Read/append the next line of input into the pattern space.
Actually, I read this line (u-boot helper.mk):
sed -n -e '/.*\.u_boot_list[^ ]\+/ ! {d;n}' \
-e 's/.*\(\.u_boot_list[^ ]\+\).*$$$$/\1/' \
-e 's/\.[^\.]\+$$$$//' \
-e ':s /^.\+$$$$/ { p;s/^\(.*\)\.[^\.]*$$$$/\1/;b s }'
The two commands exist to serve two different (although seemingly similar) purposes. In your case, however, the "n" is redundant. Recall:
The "n" command will print out the current pattern space (unless the "-n" flag is used), empty the current pattern space, and read in the next line of input.
The "d" command deleted the current pattern space, reads in the next line, puts the new line into the pattern space, and aborts the current command, and starts execution at the first sed command. This is called starting a new "cycle."
This brief was taken from the grymoire site. You can read more about working with multiple lines here.
I'm trying to find some text in an XML file and delete only a part of a line that has it.
I found this format to try: perl -p -i -e "s/$1/$2/g" $3 after some code searches.
So I'm using this code:
perl -p -i.bak -e "s/\'../../../specialText/\'//g" "C:/box/fileName.XML";
What I want to do is delete everything from the inner single quotes as in:
'../../../specialText/', but using q() or \' to escape the quote doesn't work and I'm not sure the ..'s aren't messing things up either. I'm guessing that not putting anything in as a text replacement will delete it properly, but I'm not sure.
The errors are:
Backslash found where operator expected at -e line 1, near "/specialText/\"
(Missing operator before \?)
syntax error at -e line 1, near "/specialText/\"
Can't find string terminator "'" anywhere before EOF at -e line 1.
How do rewrite this one liner to accomplish this?
This works.
C:\box>perl -p -i.bak -e s/Copyright/bar/g Test.txt
I tried it on another file, so now I just have to play with it to modify my original.
You can escape the . and / characters in the search string by putting a backslash (\) before each of them.
However, to avoid acute leaning toothpick syndrome, I'd recommend instead using alternative regexp delimiters and the \Q and \E escape sequences, like this:
perl -p -i.bak -e "s(\Q'../../../specialText/'\E)()g" "C:/box/fileName.XML"
And what's wrong with using another set of delimiters?
perl -p -i.bak -e "s{'../../../specialText/'}{}g" "C:/box/fileName.XML"
Example:
echo one two three | sed 's/ /\n/g' | sed 's/^/:/g'
output:
:one
:two
:three
Without piping:
echo one two three | sed 's/ /\n/g;s/^/:/g'
output:
:one
two
three
seems like first pattern isn't expanded before executing second one, but I really don't know much about sed
How can I use first example without piping twice?
PS Pattern used in examples is informative
The other way to do it is with repeated -e options:
echo one two three | sed -e 's/ /\n:/g' -e 's/^/:/g'
This is easier to understand when you have many operations to do; you can align the separate operations on separate lines:
echo one two three |
sed -e 's/ /\n:/g' \
-e 's/^/:/g'
For example, I have a script to generate outline documents from templates. One part of the script contains:
sed -e "s/[:]YEAR:/$(date +%Y)/g" \
-e "s/[:]TODAY:/$today/" \
-e "s/[:]BASE:/$BASE/g" \
-e "s/[:]base:/$base/g" \
-e "s/[:]FILE:/$FILE/g" \
-e "s/[:]file:/$file/g" \
$skeleton |
...
Although it could be done on one line, it would not promote readability.
The main problem here is that sed decides on what constitutes a line (a pattern that it works on) before executing any commands. That is, if you have only one pattern (one two three), it won't get reinterpreted as multiple lines after execution of s/ /\n/g. If would be still a single pattern, although that would be the one that contains newlines inside it.
The simplest workaround to make sed reinterpret patterns along the newly inserted newlines is just running sed twice, as you did.
Another workaround would be adding something like m option (multi-line buffer) to s command:
$ echo one two three | sed 's/ /\n/g;s/^/:/mg'
:one
:two
:three
You could put all that into one regular expression like this:
echo one two three | sed 's/\([^ ]\+\)\( \+\|$\)/:\1\n/g'
The first part \([^ ]\+\) selects your words (i.e. a string of characters which is not a space. The seconds part \( \+\|$\) matches either one or more spaces or the line end (which is required for the three which has no space after it.
Then we we just build the line by using a back-reference to the word matched in part 1.
This might work for you:
echo one two three | sed 'y/ /\n/;s/^/:/mg'
:one
:two
:three