29

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.

  • 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 '16 at 9:03
  • 3
    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 ^^) – Olivier Dulac Oct 21 '16 at 11:01
  • @OlivierDulac I wouldn't recommend using : in a filename. I believe Windows doesn't support it. – Justin Aug 17 '18 at 18:35
  • @Justin good point. the standard recommends dropping it for filenames. I gave the in-file version – Olivier Dulac Aug 17 '18 at 19:46
46

Install renameutils and use qmv with your favorite text editor.

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.

| improve this answer | |
  • 1
    Welcome to askUbuntu! This is so cool! – αғsнιη Oct 21 '16 at 18:55
  • Signed it just to +! it. ;) – J. Allan Oct 21 '16 at 20:30
  • 2
    Wow, nice tool and nice GIF animation. Great first answer, be welcome! :) – Byte Commander Oct 21 '16 at 20:56
  • 1
    Thanks @KasiyA for inlining the image and thanks everyone else for the positive comments. – ateijelo Oct 21 '16 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 '16 at 14:46
31

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.

| improve this answer | |
  • 2
    On some distros, this command is available as prename (p for perl, because the first arg is a perl expression) – Peter Cordes Oct 23 '16 at 5:27
12

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'
| improve this answer | |
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.

| improve this answer | |
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 .
| improve this answer | |
  • 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). – rexkogitans Oct 21 '16 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.) – Peter Cordes Oct 23 '16 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.

| improve this answer | |
  • 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.) – Peter Cordes Oct 23 '16 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 '16 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"... – Peter Cordes Oct 26 '16 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 '16 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 – Peter Cordes Oct 26 '16 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.
| improve this answer | |
  • 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 '16 at 7:54
  • Might I also suggest Thunar Bulk Renamer for a snappy interactive GUI approach. – Tony Martin Oct 25 '16 at 18:29
  • @TonyMartin This should be comment under question, this has nothing to do with the answer – Sergiy Kolodyazhnyy Jul 5 '18 at 22:19
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

| improve this answer | |

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.