Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

regex, search and replace until a certain point

The Problem

I have a file full of lines like

convert.these.dots.to.forward.slashes/but.leave.these.alone/i.mean.it

I want to search and replace such that I get

convert/these/dots/to/forward/slashes/but.leave.these.alone/i.mean.it

The . are converted to / up until the first forward slash

The Question

How do I write a regex search and replace to solve my problem?

Attempted solution

I tried using look behind with perl, but variable length look behinds are not implemented

$ echo "convert.these.dots.to.forward.slashes/but.leave.these.alone/i.mean.it" | perl -pe 's/(?<=[^\/]*)\./\//g'
Variable length lookbehind not implemented in regex m/(?<=[^/]*)\./ at -e line 1.

Workaround

Variable length look aheads are implemented, so you can use this dirty trick

$ echo "convert.these.dots.to.forward.slashes/but.leave.these.alone/i.mean.it" | rev | perl -pe 's/\.(?=[^\/]*$)/\//g' | rev
convert/these/dots/to/forward/slashes/but.leave.these.alone/i.mean.it

Is there a more direct solution to this problem?

like image 417
Pyrolistical Avatar asked Mar 24 '23 06:03

Pyrolistical


2 Answers

s/\G([^\/.]*)\./\1\//g

\G is an assertion that matches the point at the end of the previous match. This ensures that each successive match immediately follows the last.

Matches:

\G          # start matching where the last match ended
([^\/.]*)   # capture until you encounter a "/" or a "."
\.          # the dot

Replaces with:

\1     # that interstitial text you captured
\/     # a slash

Usage:

echo "convert.these.dots.to.forward.slashes/but.leave.these.alone/i.mean.it" | perl -pe 's/\G([^\/.]*)\./\1\//g'

# yields: convert/these/dots/to/forward/slashes/but.leave.these.alone/i.mean.it

Alternatively, if you're a purist and don't want to add the captured subpattern back in — avoiding that may be more efficient, but I'm not certain — you could make use of \K to restrict the "real" match solely to the ., then simply replace with a /. \K essentially "forgets" what has been matched up to that point, so the final match ultimately returned is only what comes after the \K.

s/\G[^\/.]*\K\./\//g

Matches:

\G        # start matching where the last match ended
[^\/.]*   # consume chars until you encounter a "/" or a "."
\K        # "forget" what has been consumed so far
\.        # the dot

Thus, the entirety of the text matched for replacement is simply ".".

Replaces with:

\/      # a slash

Result is the same.

like image 128
Wiseguy Avatar answered Apr 02 '23 06:04

Wiseguy


You can use substr as an lvalue and perform the substitution on it. Or transliteration, like I did below.

$ perl -pe 'substr($_,0,index($_,"/")) =~ tr#.#/#'
convert.these.dots.to.forward.slashes/but.leave.these.alone/i.mean.it
convert/these/dots/to/forward/slashes/but.leave.these.alone/i.mean.it

This finds the first instance of a slash, extracts the part of the string before it, and performs a transliteration on that part.

like image 38
TLP Avatar answered Apr 02 '23 05:04

TLP