I need to replace a character on file portions matching a certain regexp. To be precise, I need to remove spaces with - in ill-formed markdown links, e.g.:

[This is my link](actual bad link.md)

should become:

[This is my link](actual-bad-link.md)

I can match something that begins with ( and ends with .md) with a regexp in sed, but then I don't know how to tell sed to replace only the spaces in the matched expression. Is there a way to do this (possibly also with another command line tool like e.g. awk)?


Edit: to be minimal, I'd be happy with any command that replaces all the spaces between ( and .md) with -.

  • 1
    What about (x) 1.md)? Arguably the space is between ( and .md). – Quasímodo Mar 17 at 19:47
  • 2
    @ Nicola Mori you should post a representative set of input data rather than a minimal contrived one.Because the accuracy depends on that not to mention the iterations needed. – guest_7 Mar 18 at 2:07
  • 1
    Do you want to replace spaces within all (...) pairs or only those (...) pairs that immediately follow a [...] pair? – Ed Morton Mar 18 at 15:15
  • 1
    @EdMorton I had to comment (now deleted) a couple of times for clarification to wring out the last paragraph from the question, which narrows it down a bit. Yours was one of my clarification requests. Apparently the asker is not much interested in making this question well-defined, since he explicitly says that pointing out corner cases is unhelpful. Unfortunately sometimes we just have to give up. – Quasímodo Mar 18 at 18:31
  • 1
    @Quasímodo Yeah I upvoted your comment and the one from guest_7 earlier and saw that comment from the OP and was going to post a response that what's realistic or not is something only they know in their mind, all we can do is point out those non-sunny-day cases to try to help them and get clarity on their actual requirements beyond the one trivial case in the example. I haven't posted an answer due to that comment, thought I'd give them another chance to specify their requirements. – Ed Morton Mar 18 at 18:34

For a single instance per line, and no nested parentheses, you could do something like this with match and substr:

$ echo '[This is my link](actual bad link.md) other stuff' | awk '
      s = substr($0,RSTART,RLENGTH); gsub(/ /,"-", s); 
      print substr($0,1,RSTART-1) s substr($0,RSTART+RLENGTH)}
[This is my link](actual-bad-link.md) other stuff

GNU awk has a non-standard extension to capture match groups into an array, but IMO it doesn't buy you much here.

While you could loop the match to achieve global replacement, Perl might be a better option ex.

$ echo '[This is my link](actual bad link.md) other stuff' | perl -pe '
    s{(\(.*?\.md\))}{$1 =~ s/ /-/gr}ge
[This is my link](actual-bad-link.md) other stuff
  • The perl option seems to work for me . It does the job also for multiple links on the same line. Thanks! – Nicola Mori Mar 18 at 10:29

You can use the tr command tr with the -t flag takes two arguments first the character you want to replace and the second is what to replace it with.

cat data | tr -t " " "-" 

This will replace all spaces with - but it should be trivial to pull out the second half of the links. EDIT: Here is agrepcommand to pull out whats between the( )`

grep -Po '(?<=\().*(?=\))'

EDIT: This is an example how this can be done with the core utils. This script can also be shorted I used the variables to try and make it a little easier to read. grep or sed or awk any of those command could be used to produce the same output.

data="[This is my link](actual bad link.md)"
r=$(echo $data | grep -Po '(?<=\().*(?=\))')
n=$(echo $data | grep -Po '(?<=\().*(?=\))' | tr -s ' ' '-')
t=$(echo $data | sed "s|${r}|${n}|")

echo $t
  • 1
    That cannot work. tr alone will convert ALL spaces to -s, not just those in the target strings, and while grep will identify the target strings it (paired with tr or not) has no way to modify those strings and put them back into the original text. You need a tool that can actually edit text for this task. – Ed Morton Mar 18 at 15:18
  • I have to disagree with that comment I cant add lines in comments so i added an edit to my original answer this is easliy done with core utils and my script could be shortened using the variables to make it a little easier to understand. – Jason Croyle Mar 19 at 21:22
  • 1
    I didn't say you couldn't write a shell script, I said you can't do it with tr alone and you can't do it with grep even with tr and that you need a tool that can actually edit text for this task. You've added that tool that can edit text, sed, but even then it'll still fail when r contains regexp metachars, n contains backreference chars, either of them contain the delimiter chars, other strings that look like r exist earlier in the line, etc. – Ed Morton Mar 19 at 22:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.