38

I have a number of files:

10.3.100.179_01_20161018_230014_5335.jpg
10.3.100.179_01_20161018_231514_0814.jpg
10.3.100.179_01_20161018_233014_5706.jpg
10.3.100.179_01_20161018_234514_0896.jpg
10.3.100.179_01_20161018_230114_5395.jpg
10.3.100.179_01_20161018_231614_1145.jpg
10.3.100.179_01_20161018_233114_6047.jpg
10.3.100.179_01_20161018_234614_0547.jpg
10.3.100.179_01_20161018_230114_5492.jpg
10.3.100.179_01_20161018_231614_1264.jpg
10.3.100.179_01_20161018_233114_6146.jpg
10.3.100.179_01_20161018_234614_0658.jpg
10.3.100.179_01_20161018_230214_5630.jpg
10.3.100.179_01_20161018_231714_7135.jpg

I want to rename with this format:

10.4.100.135_01_20161013131108389_TIMING.jpg
10.4.100.135_01_20161013131111390_TIMING.jpg
10.4.100.135_01_20161013131114401_TIMING.jpg
10.4.100.135_01_20161013131117431_TIMING.jpg
10.4.100.135_01_20161013131120418_TIMING.jpg
10.4.100.135_01_20161013131123461_TIMING.jpg
10.4.100.135_01_20161013131126511_TIMING.jpg

It needs to remove the _ in timestamp and add the _TIMING.

5
  • 2
  • 6
    Please use consistent examples in your questions. It looks like you want to replace 10.3.100.179 with 10.4.100.135. Is that what you want or do you just want to remove the _TIMING and the _ from the time?
    – terdon
    Oct 21, 2016 at 9:03
  • 4
    A good time to remind people that there is an iso8601 standard for dates (that work both for filenames AND for log entries) ^^ So maybe you should reconsider the renaming to become: YYYY-MM-DDThh:mm:ss.mmm (ex: your first file would become: 10.4.100.135_01_2016-10-13T13:11:08.389_TIMING.jpg (and "TIMING" could then even be removed, as it now looks clearly like a date and hour (and milliseconds), especially when the standard becomes more widespread. The T is part of the standard and I grew up to like it (and taking it out breaks the standard ^^) Oct 21, 2016 at 11:01
  • 1
    @OlivierDulac I wouldn't recommend using : in a filename. I believe Windows doesn't support it.
    – Justin
    Aug 17, 2018 at 18:35
  • @Justin good point. the standard recommends dropping it for filenames. I gave the in-file version Aug 17, 2018 at 19:46

8 Answers 8

63

Install renameutils and use qmv with your favorite text editor with:

sudo apt install renameutils

qmv loads all names in your editor and when you save and close it applies your changes to the actual files. If the changes are inconsistent (e.g. two files get the same name) it will abort without touching anything. It also handles circular renames correctly.

I usually do:

$ qmv -f do

so that it shows just one column of names (do: destination-only). Here's how it looks:

qmw

If you combine it with the multiple cursors of SublimeText, Atom or Visual Studio Code, it makes a very nice and powerful tool for bulk renaming. For instance, for Atom, you would do EDITOR="atom -w" qmv -f do.

8
  • 1
    Welcome to askUbuntu! This is so cool! Oct 21, 2016 at 18:55
  • 2
    Wow, nice tool and nice GIF animation. Great first answer, be welcome! :)
    – Byte Commander
    Oct 21, 2016 at 20:56
  • 1
    Thanks @KasiyA for inlining the image and thanks everyone else for the positive comments.
    – ateijelo
    Oct 21, 2016 at 22:47
  • 1
    @kasperd Yes, it will warn that the rename plan contains errors and open an interactive console where further actions can be taken.
    – ateijelo
    Oct 22, 2016 at 14:46
  • 1
    For sublime text, use qmv --editor="sublime -w" -f do ./data/masks and then use shift+alt to select the block and delete it..
    – b-ak
    Jun 12, 2020 at 12:49
34

Use rename...

rename -n 's/^([0-9]+\.[0-9]\.[0-9]+\.[0-9]+_[0-9]+_)([0-9]+)_([0-9]+)_([0-9]+)\.jpg/$1$2$3$4_TIMING\.jpg/' *

With -n this will output what it's going to do without making any changes:

rename(10.3.100.179_01_20161018_230014_5335.jpg, 10.3.100.179_01_201610182300145335_TIMING.jpg)
rename(10.3.100.179_01_20161018_231514_0814.jpg, 10.3.100.179_01_201610182315140814_TIMING.jpg)
rename(10.3.100.179_01_20161018_233014_5706.jpg, 10.3.100.179_01_201610182330145706_TIMING.jpg)
rename(10.3.100.179_01_20161018_234514_0896.jpg, 10.3.100.179_01_201610182345140896_TIMING.jpg)

If it looks right, remove the -n

$ rename 's/^([0-9]+\.[0-9]\.[0-9]+\.[0-9]+_[0-9]+_)([0-9]+)_([0-9]+)_([0-9]+)\.jpg/$1$2$3$4_TIMING\.jpg/' *
$ ls
10.3.100.179_01_201610182300145335_TIMING.jpg  10.3.100.179_01_201610182330145706_TIMING.jpg
10.3.100.179_01_201610182315140814_TIMING.jpg  10.3.100.179_01_201610182345140896_TIMING.jpg

Explaining...

  • s/something/something_else/ search and replace
  • ^ the beginning of the name (anchoring)
  • [0-9] any number
  • + one or more of the preceeding character
  • \. literal . (without \ this matches any character)
  • () to keep this part
  • $1$2$3$3 back references to the things matched earlier and kept with ()

Note: the * at the end of the command is matching all visible files in the current directory. Use a more suitable glob if necessary.

1
  • 2
    On some distros, this command is available as prename (p for perl, because the first arg is a perl expression) Oct 23, 2016 at 5:27
15

mmv can do it as in the following:

mmv '*_*_*_*_*.jpg' '#1_#2_#3#4#5_TIMING.jpg'

10.3.100.179_01_20161018_230014_5335.jpg 10.3.100.179_01_201610182300145335_TIMING.jpg

#1, #2, #3, ... is referencing each one to the matching '*' here.

It is even shorter with:

mmv '*_*_*.jpg' '#1#2#3_TIMING.jpg'
10

Another rename approach:

$ rename -n 's/(.*)_(.*)_(.*)\./$1$2$3_TIMING./' *
10.3.100.179_01_20161018_230014_5335.jpg -> 10.3.100.179_01_201610182300145335_TIMING.jpg
10.3.100.179_01_20161018_230114_5395.jpg -> 10.3.100.179_01_201610182301145395_TIMING.jpg
10.3.100.179_01_20161018_230114_5492.jpg -> 10.3.100.179_01_201610182301145492_TIMING.jpg
10.3.100.179_01_20161018_230214_5630.jpg -> 10.3.100.179_01_201610182302145630_TIMING.jpg
10.3.100.179_01_20161018_231514_0814.jpg -> 10.3.100.179_01_201610182315140814_TIMING.jpg
10.3.100.179_01_20161018_231614_1145.jpg -> 10.3.100.179_01_201610182316141145_TIMING.jpg
10.3.100.179_01_20161018_231614_1264.jpg -> 10.3.100.179_01_201610182316141264_TIMING.jpg
10.3.100.179_01_20161018_231714_7135.jpg -> 10.3.100.179_01_201610182317147135_TIMING.jpg
10.3.100.179_01_20161018_233014_5706.jpg -> 10.3.100.179_01_201610182330145706_TIMING.jpg
10.3.100.179_01_20161018_233114_6047.jpg -> 10.3.100.179_01_201610182331146047_TIMING.jpg
10.3.100.179_01_20161018_233114_6146.jpg -> 10.3.100.179_01_201610182331146146_TIMING.jpg
10.3.100.179_01_20161018_234514_0896.jpg -> 10.3.100.179_01_201610182345140896_TIMING.jpg
10.3.100.179_01_20161018_234614_0547.jpg -> 10.3.100.179_01_201610182346140547_TIMING.jpg
10.3.100.179_01_20161018_234614_0658.jpg -> 10.3.100.179_01_201610182346140658_TIMING.jpg

If that seems to work as you want it to, remove the -n.

5

You can also use the following. First take a backup your files and try this:

find . -name "*.jpg" -type f -print0| while read -d $'\0' file
do
    #extension="${file##*.}"
    newfilename=$(echo "${file%.*}" | sed 's/\(.*\)_\(.*\)_/\1\2/')
    mv "$file" "$newfilename""_TIMING.jpg"
done

sed 's/\(.*\)_\(.*\)_/\1\2/') deletes the _ characters in the timestamp.

For example:

user@host$ ls -lart
total 8
drwxrwxr-x 6 user user 4096 Oct 21 10:21 ..
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230014_5335.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231514_0814.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_233014_5706.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_234514_0896.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230114_5395.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231614_1145.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_233114_6047.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_234614_0547.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230114_5492.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231614_1264.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_233114_6146.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_234614_0658.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_230214_5630.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_20161018_231714_7135.jpg
drwxrwxr-x 2 user user 4096 Oct 21 10:30 .

user@host$ find . -name "*.jpg" -type f -print0 | while read -d $'\0' file
> do
>  newfilename=$(echo "${file%.*}" | sed 's/\(.*\)_\(.*\)_/\1\2/')
>  mv $file $newfilename"_TIMING.jpg"
> done

10:35:20 t $ ls -lart
total 8
drwxrwxr-x 6 user user 4096 Oct 21 10:21 ..
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182300145335_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182315140814_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182330145706_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182345140896_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182301145395_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182316141145_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182331146047_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182346140547_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182301145492_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182316141264_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182331146146_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182346140658_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182302145630_TIMING.jpg
-rw-rw-r-- 1 user user    0 Oct 21 10:30 10.3.100.179_01_201610182317147135_TIMING.jpg
drwxrwxr-x 2 user user 4096 Oct 21 10:35 .
2
  • 1
    this is the best solution. Of course, Zanna's soultion is perfect for a couple of files, but since globbing is performed by the shell, their solution may fail on a large number of files (command string too long). Oct 21, 2016 at 12:06
  • @rexkogitans: If you're on a modern Linux system (i.e. any version of Ubuntu), the limit on command-line length is multiple megabytes. And if it is a problem, you can use find -maxdepth 1 -exec rename ... {} + to batch the listing of the current directory onto rename's command line. (add in -name *.jpg or whatever you want). That will execute much faster than a shell loop that forks+execs sed and mv for every filename, instead of just a rename system call. (You could get rid of sed using bash's built-in regex stuff, but forking mv is still slow-ish.) Oct 23, 2016 at 5:38
2

You're probably finished, but here's a (simple) all bash solution:
Is "simple ... bash solution" an oxymoron?

#!/bin/bash

#loop through all files ending in .jpg
for f in *.jpg;
do

    #cut out everything to the timestamp
    firsthalf=${f%_*_*_*}

    #get from the timestamp on
    lasthalf=${f#*_*_}

    #remove (all) underscores from timestamp
    #note the 2 forward slashes...
    lasthalf=${lasthalf//_/}

    #get our extension
    ext=${lasthalf##*.}

    #now we can remove the extension
    lasthalf=${lasthalf%.*}

    #rename the file
    #change `mv` to `echo` if you want to do a trial run first...
    mv "$f" "${firsthalf}_${lasthalf}_TIMING.${ext}"

done;

PS: The logic in the loop was tested with one of your example filenames. It passed.

9
  • 1
    Probably better to use a bash regex with [[ $f ~= (.*)_(.*)_(.*)_(.*)\.jpg ]]; newname=${BASH_REMATCH[1]${BASH_REMATCH:2:4}TIMING.jpg or something. (totally untested and probably wrong, but the general idea is that capture groups go into the BASH_REMATCH array.) Oct 23, 2016 at 5:44
  • @PeterCordes: That is a good point! I've never used (bash) capturing groups, and to be perfectly honest, bash isn't the first language with which I'd try them. (I think bash is an ugly language.) Still, it is a good solution and it taught me something so kudos.
    – J. Allan
    Oct 26, 2016 at 0:42
  • Yup, the main reason to do that much text processing in pure bash is when writing tab-completion functions that should be fast. It brings a whole new meaning to the phrase "ugly code"... Oct 26, 2016 at 1:31
  • @PeterCordes: I get your point, although I don't think that code is "ugly." It may not be the 2 line solution you gave, but it is not hard to read as far as I'm concerned; plus it's well commented...
    – J. Allan
    Oct 26, 2016 at 2:10
  • I was talking about the bash-completion code in general being ugly (or at least hard to read). It's actually not really ugly, just somewhat mind-bending (e.g. passing args by ref in bash done by passing a variable name and having the callee use printf -v "$3" ... to set the variable whose name is in $3.) Taking apart the whole command line and putting it back together is pretty hard to follow / debug, at least I found it that way while trying to clean it up / fix bugs. See the code on github Oct 26, 2016 at 2:16
1

If you could use GUI, I would recommend pyRenamer.

It's present in most distributions, f.i. in Ubuntu:

sudo apt-get install pyrenamer

It can do all you want and more.

  • It can use patterns, add or suppress text.
  • It can access to the EXIF data if renaming photos, so you can generate patterns based on date/time, etc...
  • Can use some metadata if renaming music files.
  • Moreover, it has a preview, which can prevent some mistakes difficult to revert.
4
  • To the downvoter... Care to explain why? OP did not specify if GUI was an option or not. I see most answers go to the console/script way, but many users will appreciate a GUI solution. Or maybe pyRenamer has some flaw I am not aware of. In any case, I like to know the reasons for downvotes.
    – jrierab
    Oct 24, 2016 at 7:54
  • Might I also suggest Thunar Bulk Renamer for a snappy interactive GUI approach. Oct 25, 2016 at 18:29
  • @TonyMartin This should be comment under question, this has nothing to do with the answer Jul 5, 2018 at 22:19
  • This is abandoned and cannot be found in official repos anymore. You can try to install it from sources, even if it may be a bit tricky
    – Chris
    Mar 8 at 8:16
0

Here's your raw built-in find + xargs + sed + mv oneliner (love oneliners):

find . -name "*.jpg" -print0 | sort -z | xargs -0 sh -c 'for filename; do mv "$filename" $(echo "${filename}" | sed "s/\([0-9]\{8\}\)_\([0-9]\{6\}\)_\([0-9]\{4\}\)/\1\2\3_TIMING/g"); done' sh

Explanation:

  • find . -name "*.jpg" | sort | xargs sh -c <command> sh : list all JPEGs in the current directory then execute a shell command for each one (sorting is optional of course, but keep things a little cleaner if you are logging somewhere)

  • -print0, -z, -0 : it is good habit to separate items with binary 0 when tokenizing file names to avoid problems with in-the-middle white-spaces (not your case though)

  • mv "$filename" $(echo "${filename}" | sed "s/\([0-9]\{8\}\)_\([0-9]\{6\}\)_\([0-9]\{4\}\)/\1\2\3_TIMING/g"); : (backslashes in the sed's regex do not help its readability, but it is simple) rename each file by replacing the underscore-separeted sequence of 8+6+4 digits with their contiguous concatenation plus this _TIMING thing (\i is a backreference to the i-th regex group).


Refs: Xargs - man sed

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.