2

I'm trying to modify a markdown file. In a file, there are many links like this one.

[string one](/stringtwo/#stringthree)

I'd like to change these to the following:

[string one](stringtwo.html#stringthree)

Remove slashes and add .html.

I tried the following:

sed -i 's/](\(\/.*\)#/](\1.html#/g' file

But it returns [global configuration](/config/.html#globals). It doesn't remove slashes.

How can I achieve this using bash or sed?

3 Answers 3

5

This seems to do the trick:

$ cat 725364.in
[string one](/stringtwo/#stringthree)
[example label](/path/to/doc/#anchor)
$  sed 's_\(\[[^]]*]\)(/\([^#]*\)/\(#[^)]*\))_\1(\2.html\3)_g' 725364.in
[string one](stringtwo.html#stringthree)
[example label](path/to/doc.html#anchor)

To break it down:

Firstly, I use s_needle_pin_flags for sed rather than s/needle/pin/flags so as not to have to escape literal /s.

sed will search for, using this expression \(\[[^]]*]\)(/\([^#]*\)/\(#[^)]*\)), broken down as:

  • \(\[[^]]*]\) - Definition of group 1 (the link label):
    • A literal [
    • Followed by zero or more or of anything that is not a ]
    • Followed by a literal ]
  • (/ - A literal (/
  • \([^#]*\) - Definition of group 2 (the URL):
    • Zero or more of anything that is not a literal #
  • / - A literal /
  • \(#[^)]*\) - Definition of group 3 (the anchor):
    • A literal #
    • Followed by zero or more of anything that is not a literal )
  • ) - A literal )

And transform it using \1(\2.html\3), broken down as:

  • The match for group 1, followed by
  • (, followed by
  • The match for group 2, followed by
  • .html, followed by
  • The match for group 3, followed by
  • )
3

The group \1 matched in 's/](\(\/.*\)#/](\1.html#/g' does really contain the slashes. You should specify the \/ outside the group \(\/.*\). Try this:

sed -i 's/](\/\(.*\)\/#/](\1.html#/g' file 
3

Using Raku (formerly known as Perl_6)

~$ raku -pe 's{ \( ~ \) [(\/) ( .+? ) (\/) (\# .+ )] $ } = "($1.html$3)";'  725364.in

#OR (more formally)

~$ raku -pe 's{ \( ~ \) [(\/) ( <-[#]>+? ) (\/) (\# <-[#]>+ )] $ } = "($1.html$3)";'  725364.in

Sample Input (thanks to @DopeGhoti):

[string one](/stringtwo/#stringthree)
[example label](/path/to/doc/#anchor)

Sample Output:

[string one](stringtwo.html#stringthree)
[example label](path/to/doc.html#anchor)

Because the OP wants to change / forward-slashes, a different syntax than the regular s/// Raku substitution syntax is used. Raku also offers a s{ … } = " … " substitution syntax, which is used above (or s[ … ] = " … ", etc.).

In the first atom Raku's ~ tilde syntax for nested data structures is used. Basically the regex \( ~ \) [ … ] says "the group in the square brackets is surrounded by parentheses". Then the regex goes on to capture a / forward slash into $0, a non-greedy sequence of characters into $1, a second/ forward slash into $2, and a terminal greedy sequence of characters initiated by a # octothorpe into $3.

Display of captures (change -pe flags to -ne and add say):

~$ perl6 -ne 'say s{ \( ~ \) [(\/) ( .+? ) (\/) (\# .+ )] $ } = "($1.html$3)";'  725364.in
「(/stringtwo/#stringthree)」
 0 => 「/」
 1 => 「stringtwo」
 2 => 「/」
 3 => 「#stringthree」
「(/path/to/doc/#anchor)」
 0 => 「/」
 1 => 「path/to/doc」
 2 => 「/」
 3 => 「#anchor」

In the replacement, non-captured parentheses are restored. The captured / forward-slashes are dropped. The captured path in $1 is output, followed by .html then followed by $3 which is the captured # octothorpe-initiated, terminal string.

https://docs.raku.org/language/regexes
https://raku.org

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .