Hi everyone !
I’m in need for some assistance for string manipulation with sed and regex. I tried a whole day to trial & error and look around the web to find a solution however it’s way over my capabilities and maybe here are some sed/regex gurus who are willing to give me a helping hand !
With everything I gathered around the web, It seems it’s rather a complicated regex and sed substitution, here we go !
What Am I trying to achieve?
I have a lot of markdown guides I want to host on a self-hosted forgejo based git markdown. However the classic markdown links are not the same as one github/forgejo…
Convert the following string:
[Some text](#Header%20Linking%20MARKDOWN.md)
Into
[Some text](#header-linking-markdown.md)
As you can see those are the following requirement:
- Pattern:
[
]( - Only edit what’s between parentheses
- Replace
space (%20)
with-
- Everything as lowercase
- Links are sometimes in nested parentheses
- e.g. (look here
[
) ](
- e.g. (look here
- Do not change a line that begins with
https
(external links)
While everything is probably a bit complex as a whole the trickiest part is probably the nested parentheses :/
What I tried
The furthest I got was the following:
sed -Ei 's|\(([^\)]+)\)|\L&|g' test3.md #make everything between parentheses lowercase
sed -i '/https/ ! s/%20/-/g' test3.md #change every %20 occurrence to -
These sed/regx substitution are what I put together while roaming the web, but it has a lot a flaws and doesn’t work with nested parentheses. Also this would change every %20
occurrence in the file.
The closest solution I found on stackoverflow looks similar but wasn’t able to fit to my needs. Actually my lack of regex/sed understanding makes it impossible to adapt to my requirements.
I would appreciate any help even if a change of tool is needed, however I’m more into a learning processes, so a script or CLI alternative is very appreciated :) actually any help is appreciated :D !
Thanks in advance.
Okay. To address the
%20
and thehttps
links, and theplaceholder
links, I came up with a bash script to handle this.Because of the variation in the links, instead of trying to write a
sed
command that will match only%20
in anchor markdown links, and placeholder links, while ignoring https links and ignoring all other text in the document.To do that, I used
grep
, awhile
loop,IFS
, andsed
Here’s the script:
#! /bin/bash mdlinks="$(grep -Po ']\((?!https).*\)' ~/mkdn" while IFS= read -r line; do dashlink="$(echo "$line" | sed 's/%20/-/g')" sed -i "s/$line/${dashlink}/" /path/to/file done <<<"$mdlinks"
I’m not sure how familiar you are with bash scripting, so I’ll do the same breakdown:
#! /bin/bash
- This tells the shell what interpreter to use for the script. In this case it’s bash.mdlinks="$(grep -Po ']\((?!https).*\)' /path/to/file"
- This line usesgrep
to search for markdown link enclosures excluding https links and to output only the text that matches and saves all of that into a variable calledmdlinks
. Each link match will be a new line inside the variable.The breakdown of the
grep
command is as followes:grep
- invokes the grep command-Po
- two command flags. TheP
tellsgrep
to use perl regular expressions. Theo
tells grep to only print the output that matches, rather than the entire line.'
- opens the regex statement]\(
- finds a closing bracket followed by an opening parentheses(?!https)
- This is a negative look ahead, which a feature available in perl regex. This tells grep not to match if it finds thehttps
string. The parentheses encloses the negative look ahead. The?!
Is what indicates it’s a negative look ahead, and thehttps
is the string to look for and ignore.'
- closes the regex statement/path/to/file
- the file to search for matcheswhile IFS= read -r line; do
- this invokes awhile
loop using the Internal Field Separator (IFS=
), which by default includes newline character. This allows the loop to take in the variable containing all of the matched links and separate them line by line to work on one at a time. Theread
command does what it says and reads the input given. In this case our variablemdlinks
. The-r
flag tellsread
to ignore the backslash character and just treat it as a normal part of the input.line
is the variable that each line will be saved in as they are worked through the loop. The;
endswhile
setup, anddo
opens the loop for the commands we want to run using the input saved inline
.dashlink="$(echo "$line" | sed 's/%20/-/g')"
- This command sequence runs the markdown link saved in theline
variable into sed to find all instances of%20
and replace them with a-
.dashlink
- the variable we’re saving the new link with dashes to.=
- separates the variable from the input being saved into the variable."
- opens this command string for variable expansion.$
- tellsbash
to do command substition, meaning that the output of the following commands will be saved to the variable, rather than the actual text of the commands that follows.(
- opens the command setecho
- prints the given text or arguments to standard output, in this case the given argument is the variable$line
"
- tellsbash
to expand any variables contained within the quote set while ignoring any nonstandard characters like spaces or special shell characters that are saved in the variable.$line
- the variable containing our active markdown link from the text document"
- the closing quote ending the argument and the expansion enclosure|
- This is a pipe, which redirects the standard output of the command on the left into the command on the right. Meaning we’re taking the markdown link currently saved in the variable and feeding it intosed
sed
- invokessed
so we can manipulate our text, and becausesed
is receiving redirected input, and we’ve specified no flags, the modified text will be printed to standard output.'s/%20/-/g'
- Our pattern match/substitution, which will find all occurrences of the string%20
in the markdown link fed into sed and replace them with-
.)"
- closes our command sequence for command substitution, and the variable expansion. At this point the text printed to standard output bysed
is saved to the variabledashlink
The next line is:
sed -i "s/$line/${dashlink}/" /path/to/file
, which usessed
to take theline
anddashlink
variables and use them to find the exact original markdown link in the text containing the%20
sequences, and replace it with the properly formatted markdown link with dashes.sed -i
- invokessed
and uses the-i
flag to edit the file in place."
- The double quote enclosure allows the expansion of variables in the pattern match/replacement sequence so it searches for the markdown link, and not the literal text string$line
.s/
- opens our match/modify sequence.$line
- the original markdown link that will be found/
- ends the pattern matching section${dashlink}
- The variable containing the previously modified markdown link that now has dashes. This expands to that properly formatted link which will be written into the text file replacing the malformed link. I don’t know why this link has to be enclosed in curly braces while the first one does not./"
- ends the text modification section and closes the variable expansion./path/to/file
- the file to be worked onFinally we have
done<<<"$mdlinks"
, which ends the while loop and feeds themdlinks
variable into it.done
- closes thewhile
loop<<<
- This feeds the given argument into thewhile
loop for processing"
- expands the variable within while ignoring nonstandard characters$mdlinks
- the variable we’re feeding in with all of our links containing%20
, except for https links."
- closes the variable expansion.If you’ve never written/created your own bash script, here’s what you need to do.
in your home directory, or in the directory you’re working in with these files, use a text editor like vim or nano or gedit or kate or whatever plain text editor you want to to create a new file. Call the file whatever you want.
Paste the entirety of the script text into the file. Modify the file paths as needed to work the file you want to work. if working multiple files, you’ll need to update the script for each new file path as you finish one and move on to the next
Save and exit the file
Make the file executable at the terminal with
sudo chmod +x /path/to/script/file
To run it:
. ./name-of-script-file