So I have a directory with nested folders, and at some point there are .png images.

The naming of these images is like this "letters_numbers_numbers_letters.png", and I want to delete the underscore between the numbers.

Say that we have a file named a_1_2_b.png, I want to rename it to a_12_b.png. I know I have to use something like:

find . -name "*[0-9]_[0-9]* -type f -exec bash -c <enter_code here>`

but I'm not sure how to specify how to delete this specific underscore. Remember there are two other underscores I don't want to touch.

Thanks in advance!

1 Answer 1


Use one of the variants of perl's rename. For instance, with that one (rename package in Debian-like systems):

LC_ALL=C find . -name '*[0-9]_[0-9]*' -type f -exec rename -n -d '
  s/(?<=\d)_(?=\d)//g' {} +

(remove the -n (dry-run) when happy).

With zsh, you could do:

autoload -Uz # best in ~/.zshrc
zmv -n  '(**/)(*[0-9]_[0-9]*)(#qD.)' \

It's a bit more complicated, as there's no equivalent of perl's look-around regexp operators in zsh globs.

Those remove all the _s that are surrounded by digits in every regular file's name (would rename the dir_1_2_3/file_1_2_foo_22_3_4_bar file to dir_1_2_3/file_12_foo_2234_bar for instance).

To only remove the second _ in regular files that follow the letters_digits_digits_letters.png pattern, that would just be:

zmv -n '(**/)([[:alpha:]]##_<->)_(<->_[[:alpha:]]##.png)(#qD.)' \
  • Worked like a charm! Thanks a lot! I'm refering to the first solution. I tried it to some files i made for test, and no problems there, so I went on with the real thing and all went smooth.
    – Fjolfrin
    Nov 1, 2021 at 9:03
  • You used LC_ALL=C - are there really locales with non-digit characters between 0 and 9 or is this a habit of prudence?
    – FelixJN
    Nov 1, 2021 at 10:41
  • 1
    @FelixJN, there are certainly systems and locales where [0-9] matches a lot more than 0123456789, though it's often characters that have something to do with digits (like digits in other scripts or things like ² or 🆢), but here that LC_ALL=C is more so that * matches any sequence of bytes. Nov 1, 2021 at 10:47

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.