155

I have a folder with a series of files named:

prefix_1234_567.png
prefix_abcd_efg.png

I'd like to batch remove one underscore and the middle content so the output would be:

prefix_567.png
prefix_efg.png

Relevant but not completely explanatory:

3
  • look at NSString and NSMutableString's range and substring methods Commented Jun 8, 2014 at 4:35
  • 1
    Have you considered non-terminal solutions to this? An Automator workflow can perform these types of operations with ease. You could create an automator workflow to rename all of the files and replace the text with _*_ with a blank.
    – Encryptic
    Commented Jun 8, 2014 at 4:40
  • 1
    i thought automator couldn't do wildcards
    – kidnim
    Commented Jun 8, 2014 at 18:31

9 Answers 9

273

In your specific case you can use the following bash command (bash is the default shell on macOS):

for f in *.png; do echo mv "$f" "${f/_*_/_}"; done

Note: If there's a chance that your filenames start with -, place -- before them[1]:
mv -- "$f" "${f/_*_/_}"

Note: echo is prepended to mv so as to perform a dry run. Remove it to perform actual renaming.

You can run it from the command line or use it in a script.

  • "${f/_*_/_}" is an application of bash parameter expansion: the (first) substring matching pattern _*_ is replaced with literal _, effectively cutting the middle token from the name.
  • Note that _*_ is a pattern (a wildcard expression, as also used for globbing), not a regular expression (to learn about patterns, run man bash and search for Pattern Matching).

If you find yourself batch-renaming files frequently, consider installing a specialized tool such as the Perl-based rename utility. On macOS you can install it using popular package manager Homebrew as follows:

brew install rename

Here's the equivalent of the command at the top using rename:

rename -n -e 's/_.*_/_/'  *.png

Again, this command performs a dry run; remove -n to perform actual renaming.

  • Similar to the bash solution, s/.../.../ performs text substitution, but - unlike in bash - true regular expressions are used.

[1] The purpose of special argument --, which is supported by most utilities, is to signal that subsequent arguments should be treated as operands (values), even if they look like options due to starting with -, as Jacob C. notes.

5
  • 4
    great answer, worked perfectly. i also appreciate that you went back and edited it to explain it more and provide a more elegant solution if i repeat this. again, much thanks, very helpful
    – kidnim
    Commented Jun 8, 2014 at 18:34
  • 4
    An example using matched replacements (.* -> $1), e.g. Rename Foo bar S01E01 biz baz.ext to S01E01.ext: rename -n -e 's/.*(S[0-9]{2}E[0-9]{2}).*(\.[a-z]{2,4})/$1$2/' * Commented Jan 6, 2016 at 7:48
  • 3
    Love the idea of using echo for dry runs. Thanks for the tip! Commented Jun 22, 2018 at 12:59
  • 4
    Highly recommend just doing the brew install rename Commented Apr 18, 2020 at 19:50
  • Google and SO still give better answers sometimes
    – Paul Odeon
    Commented May 24, 2023 at 9:27
126

To rename files, you can use the rename utility:

brew install rename

For example, to change a search string in all filenames in current directory:

rename -nvs searchword replaceword *

Remove the 'n' parameter to apply the changes.

More info: man rename

7
  • 2
    OS X versions of GNU bash (x86_64-apple-darwin) doesn't include the rename utility.
    – l'L'l
    Commented Jul 31, 2015 at 1:42
  • 6
    Thanks, yes it needs to be installed via brew install rename Commented Jul 31, 2015 at 1:50
  • 3
    This tip is awesome! I typed rename -vs GLYCOPHORIN GLYCC * and now it's automatically renaming 450+ files. And super fast too. Commented Jun 21, 2016 at 11:41
  • 4
    Excellent! Love the "show what would be changed" part BEFORe it actually renames things!
    – Ben Duffin
    Commented Jul 13, 2016 at 15:08
  • 3
    You can use a regex as well. rename 's/123/onetwothree/g' *
    – Bryan
    Commented Aug 10, 2016 at 17:52
18

You could use sed:

ls * | sed -e 'p;s@_.*_@_@g' | xargs -n2 mv

result:

prefix_567.png prefix_efg.png

*to do a dry-run first, replace mv at the end with echo

Explanation:

  • e: optional for only 1 sed command.
  • p: to print the input to sed, in this case it will be the original file name before any renaming
  • @: is a replacement of / character to make sed more readable. That is, instead of using sed s/search/replace/g, use s@search@replace@g
  • _.* : the underscore is an escape character to refer to the actual '.' character zero or more times (as opposed to ANY character in regex)
  • -n2: indicates that there are 2 outputs that need to be passed on to mv as parameters. for each input from ls, this sed command will generate 2 output, which will then supplied to mv.
2
  • 1
    Clever, if somewhat arcane solution. Making this more general, so as to also support filenames with embedded spaces, gets even more arcane: ls * | xargs -I % bash -c 'echo mv "%" "$(sed 's@_.*_@_@' <<<"%")"' (remove echo to actually rename).
    – mklement0
    Commented Jun 8, 2014 at 15:25
  • 1
    I found this useful for renaming and moving files that were named with \ characters (they should have been /, and frankly, in those directories instead of with descriptive names). For anyone looking for something so esoteric, it looks like ls * | sed -e 'p;s@\\@\\\\\\\\@g' | xargs -n1 echo | sed -e 'p;s@\\\\@@g' | xargs -pn2 mv, where -p displays the command to execute and asks for confirmation (y to confirm).
    – AJFarkas
    Commented Oct 11, 2023 at 1:15
9

I had a batch of files that looked like this: be90-01.png and needed to change the dash to underscore. I used this, which worked well:

for f in *; do mv "$f" "`echo $f | tr '-' '_'`"; done
4

try this

for i in *.png ; do mv "$i" "${i/remove_me*.png/.png}" ; done

Here is another way:

for file in Name*.png; do mv "$file" "01_$file"; done
3

you can install rename command by using brew. just do brew install rename and use it.

3

Using mmv

mmv '*_*_*' '#1_#3' *.png
1
  • 2
    Given this was OS X, the correct installation procedure is brew install mmv. Commented Feb 8, 2018 at 3:58
1

Since programmatically renaming files is risky (potentially destructive if you get it wrong), I would use a tool with a dry run mode built specifically for bulk renaming, e.g. renamer.

This command operates on all files in the current directory, use --dry-run until you're confident the output looks correct:

$ renamer --find "/(prefix_)(\w+_)(\w+)/" --replace "$1$3" -e name --dry-run *

Dry run

✔︎ prefix_1234_567.png → prefix_567.png
✔︎ prefix_abcd_efg.png → prefix_efg.png

Rename complete: 2 of 2 files renamed.

Plenty more renamer usage examples here.

1

zsh is now the default shell for macOS and it has its own bulk renaming feature, zmv which is sometimes aliased as mmv to noglob zmv -W.

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