I would like to change multiple mercurial hgrc files.
Starting file (example):
# example repository config (see "hg help config" for more info)
[paths]
default = http://ourmercurialserver.de/apps/
# path aliases to other clones of this repo in URLs or filesystem paths
...
Wanted result
# example repository config (see "hg help config" for more info)
[paths]
default = ssh://myusername@ourmercurialserver.de//path/to/repositories/apps/
default-push = http://ourmercurialserver.de/apps/
# path aliases to other clones of this repo in URLs or filesystem paths
...
I have created several commands. But the first doesn't work on Mac
Insert second line default-push with found expression default = http(*):
find . -type f -name 'hgrc' -exec sed -i '' s/((^)default = http(.*)($))/\1\n\default-push = \1/ {} +
Error: -bash: syntax error near unexpected token `(' (Error occurs with (^) and ($) and without)
Replace default-push = default = http with default-push = http:
find . -type f -name 'hgrc' -exec sed -i '' s/((^)default-push = default = http)/default-push = http/ {} +
Does work :-) Replace the default http-String to an ssh-String:
find . -type f -name 'hgrc' -exec sed -i '' s/default = http:\\/\\/ourmercurialserver.de/ssh:\\/\\/default = myusername@ourmercurialserver.de\\/\\/path\\/to\\/repositories/ {} +
Edit: My final Solution (Thanks to Torek & meistermuh)
Insert second line default-push
find . -type f -name 'hgrc*' -exec sed -i '' -e 's,^default = http://\(.*\),default = http://nkoch@\1\
default-push = http://\1,' {} +
Replace the default http-String to an ssh-String
find . -type f -name 'hgrc' -exec sed -i '' 's,^default = http://myusername@ourmercurialserver.de,default = ssh://myusername@ourmercurialserver.de//path/to/repositories,' {} +
(N.B.: This is not really a Mercurial issue, it's mainly just a shell and sed issue, with a side effect of regular expression stuff. If you wanted to get particularly fancy, though, you might want to make sure that only default = http://...
lines that are in a paths
section are modified, and that requires much fancier work in sed
, or using a real config-file reader.)
You have multiple issues here:
The shell likes to interpret and/or eat certain characters.
The precise details depend on which shell you use. Most shells share a simple convention, though: double quotes quote single quotes, and quote some other special characters such as *
, but do not quote variable-name expansions like $var
and other $
-based expansions such as $(subcommand)
. Meanwhile, single quotes quote everything except single quotes. (And, outside of either kind of quote, a backslash quotes one character, including another backslash or either kind of quote, so that if you just need to get one quote in, \" or \' will do the trick.)
Parentheses are among the characters that shells tend to eat, so:
echo (foo)
is not a valid command. You must quote the parentheses, e.g.:
sh-3.2$ echo '(foo)'
(foo)
or:
sh-3.2$ echo \(foo\)
(foo)
Note that with \(
, the echo
command sees only the parenthesis—the backslash is gone (eaten by the shell) by the time echo
runs.
sed
needs to see \(...\)
around regular expressions that must be captured. These are literal backslashes and parentheses. The same goes for replacements. The entire s/match/replace/
sequence must be a single word. Hence this works:
sh-3.2$ echo test | sed -e s/\\\(e.\*\\\)/captured\ \\\1/
tcaptured est
but it's much more readable as:
sh-3.2$ echo test | sed -e 's/\(e.*\)/captured \1/'
tcaptured est
Using single quotes here means that we can use any except single quote. This allows us to use $
if desired. There is no need here (since .*
is a "maximal-munch") but in general if you do not want $
interpolation, you should use single quotes anyway.
If your goal is to replace a line that reads:
default = http://whatever
with two lines, you have a bit of a problem. Let's start with something simpler:
sh-3.2$ echo test | sed -e 's/\(e.*\)/
> captured \1/'
sed: 1: "s/\(e.*\)/
captured \1/
": unescaped newline inside substitute pattern
This particular variant of sed
(on my Mac) needs to see a backslash before the newline. Hence:
sh-3.2$ echo test | sed -e 's/\(e.*\)/\
captured \1/'
t
captured est
(Other variants of sed
may allow newlines without prefix backslashes.)
(Minor) We'd probably like to use /
in the pattern. We could write this as, e.g., s/foo\/bar/foo2\/bar2/
. Fortunately we do not have to use s/foo/bar/
, we can use any other character that doesn't occur in the pattern such as s,foo/bar,foo2/bar2,
.
Fortunately find
does not pass arguments directly to the shell. If it did you'd have a fifth problem, but we have now covered all the problems. :-) We can now first write a sed
command that works, and test it:
sh-3.2$ echo default = http://foo > foo.txt
sh-3.2$ sed -e 's,^default = http://\(.*\),default = ssh://myusername@\1\
> default-push = http://\1,' < foo.txt
default = ssh://myusername@foo
default-push = http://foo
This seems to work, so now we can have the command do the in-place editing by adding -i ''
:
sh-3.2$ sed -i '' -e 's,^default = http://\(.*\),default = ssh://myusername@\1\
default-push = http://\1,' foo.txt
sh-3.2$ cat foo.txt
default = ssh://myusername@foo
default-push = http://foo
Note: I've used your example from your "Wanted result" text, which contradicts the example in the question subject. Make sure your final command sequence does what you want before running it (or, as meistermuh suggested in a comment, keep backups—maybe use something other than -i ''
!).