6

I'm trying to use a negative lookahead in perl in command line:

echo 1.41.1 | perl -pe "s/(?![0-9]+\.[0-9]+\.)[0-9]$/2/g"

to get an incremented version that looks like this:

1.41.2

but its just returning me:

![0-9]+\.[0-9]+\.: event not found

i've tried it in regex101 (PCRE) and it works fine, so im not sure why it doesn't work here

Allen
  • 3,601
  • 10
  • 40
  • 59
  • 2
    Btw, if you want to increment then do that, not hard-code a number. (Also, why lookarounds? Just match and discard with `\K`) -- `s/[0-9]+\.[0-9]+\.\K([0-9]+)/$1+1/e;` – zdim Jun 19 '19 at 05:11
  • See also: https://stackoverflow.com/questions/10221835, https://stackoverflow.com/questions/11025114, https://stackoverflow.com/questions/25003162, https://stackoverflow.com/questions/43987641... – Wiktor Stribiżew Jun 19 '19 at 07:47

4 Answers4

6

In Bash, ! is the "history expansion character", except when escaped with a backslash or single-quotes. (Double-quotes do not disable this; that is, history expansion is supported inside double-quotes. See Difference between single and double quotes in Bash)

So, just change your double-quotes to single-quotes:

echo 1.41.1 | perl -pe 's/(?![0-9]+\.[0-9]+\.)[0-9]$/2/g'

and voilà:

1.41.2
blackbrandt
  • 2,010
  • 1
  • 15
  • 32
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • You can also do `set +H` to disable `!` history expansion in the current shell. And `shopt -s histreedit` will let you edit a failed history expansion instead of just throwing the typed command away. – melpomene Jun 19 '19 at 05:48
4

I'm guessing that this expression also might work:

([0-9.]+)\.([0-9]+)

Test

perl -e'
    my $name = "1.41.1";
    $name =~ s/([0-9.]+)\.([0-9]+)/$1\.2/;
    print "$name\n";
    '

Output

1.41.2

Please see the demo here.

ikegami
  • 367,544
  • 15
  • 269
  • 518
Emma
  • 27,428
  • 11
  • 44
  • 69
4

If you want to "increment" a number then you can't hard-code the new value but need to capture what is there and increment that

echo "1.41.1" | perl -pe's/[0-9]+\.[0-9]+\.\K([0-9]+)/$1+1/e'

Here /e modifier makes it so that the replacement side is evaluated as code, and we can +1 the captured number, what is then substituted. The \K drops previous matches so we don't need to put them back; see "Lookaround Assertions" in Extended Patterns in perlre.

The lookarounds are sometimes just the thing you want, but they increase the regex complexity (just by being there), can be tricky to get right, and hurt efficiency. They aren't needed here.

The strange output you get is because the double quotes used around the Perl program "invite" the shell to look at what's inside whereby it interprets the ! as history expansion and runs that, as explained in ruakh's post.

zdim
  • 64,580
  • 5
  • 52
  • 81
0

As an alternate to lookahead, we can use capture groups, e.g. the following will capture the version number into 3 capture groups.

(\d+)\.(\d+)\.(\d+)

If you wanted to output the captured version number as is, it would be:

\1.\2.\3

And to just replace the 3rd part with the number "2" would be:

\1.\2.2

To adapt this to the OP's question, it would be:

$ echo 1.14.1 | perl -pe 's/(\d+)\.(\d+)\.(\d+)/\1.\2.2/'
1.14.2
$
Stephen Quan
  • 21,481
  • 4
  • 88
  • 75