最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

bash - regex replace all dots exept last one doesn't work - Stack Overflow

programmeradmin0浏览0评论

I try to rename a bunch of filenames so that the periods (exept the last one) got replaced with a hypen using sed.

I tried it this way (see below), but it doesn't work... Whats wrong with the script?

echo "test.test.test.txt" | sed -e "s/\.(?=.*\.)/-/g"

results in:

test.test.test.txt

instead of the desired:

test-test-test.txt

I try to rename a bunch of filenames so that the periods (exept the last one) got replaced with a hypen using sed.

I tried it this way (see below), but it doesn't work... Whats wrong with the script?

echo "test.test.test.txt" | sed -e "s/\.(?=.*\.)/-/g"

results in:

test.test.test.txt

instead of the desired:

test-test-test.txt

Share Improve this question asked Feb 6 at 12:50 Gerco-KeesGerco-Kees 731 silver badge4 bronze badges 3
  • 2 You could reverse the string, and replace all but the first: echo ... | rev | sed 's/\./-/2g' | rev – pmf Commented Feb 6 at 12:58
  • 3 Standard sed doesn't grok (?= - see Basic Regular Expressions. – Toby Speight Commented Feb 6 at 13:01
  • 1 Do you have to "load" those filenames into shell variables or are you just modifying a stream of filenames without doing anything with them? – Fravadona Commented Feb 6 at 13:35
Add a comment  | 

7 Answers 7

Reset to default 3

Assuming that you want to rename those files with mv and that each filename will be in the variable $file, then you can use a bash regex to split it into parts and a bash parameter expansion to convert the . into -:

#!/bin/bash

file=test.test.test.txt

[[ $file =~ ^(.+)(\..+)$ ]] &&
echo mv -- "$file" "${BASH_REMATCH[1]//./-}${BASH_REMATCH[2]}"

output:

mv -- test.test.test.txt test-test-test.txt

Using any sed you could change all .s to -s then the last - back to a .:

$ echo "test.test.test.txt" | sed 's/\./-/g; s/-\([^-]*\)$/.\1/'
test-test-test.txt

With GNU awk or other that supports the non-POSIX extension of a 3rd arg to match() and gensub() you could do:

$ echo "test.test.test.txt" | awk 'match($0,/(.*)(\..*)/,a){ print gensub(/\./,"-","g",a[1]) a[2] }'
test-test-test.txt

Sed doesn't support lookahead, as that's not part of a Basic Regular expression.

There are a few options available to get what you want:

  1. Use Perl instead:

    perl -pe 's/\.(?=.*\.)/-/g'
    
  2. Reverse the string, and replace all . except the first one, then reverse it back again:

    rev | sed 's/\./-/2g' | rev
    
  3. Replace one at a time in loop until there's no longer two dots in the pattern space:

    #/usr/bin/sed -f
    :a
    /\..*\./  s/\./-/
    ta
    
$ echo "test.test.test.txt" | sed -E 's/(.*)\.(.*)/\1\n\2/;s/\./-/g;s/\n/./'
test-test-test.txt

Replace last dot with newline, then all dots with -, then newline with dot.

Using GNU awk, change the field separator from . to - for all fields except last:

$ echo "test.test.test.txt" | awk -v FS='.' -v OFS='-' '{print $1,$2,$3 "." $4}'
test-test-test.txt

Using GNU sed

$ echo "test.test.test.txt" | sed -E ':a;s/([^.]*)\.(.*\.)/\1-\2/;ta'
test-test-test.txt

This might work for you (GNU sed):

sed -E 's/(.*)\./\1\n/;y/\n./.-/' file

Use greed to select the last . and rename it (in this case to \n a newline).

Then use translate to replace all other .'s with -'s and replace the renamed . (I used a newline as it cannot be present in a pristine pattern space) back to ..

发布评论

评论列表(0)

  1. 暂无评论