Move files based of a comparison with a file - bash

I have 1000 files with following names:
something-345-something.txt
something-5468-something.txt
something-100-something.txt
something-6200-something.txt
and a lot more...
And I have one txt file, with only numbers in it. f.e:
1000
500
5468
6200
699
usw...
Now I would like to move all files, which have a number in their filenames which is in my txt file.
So in my example above the following files should be moved only:
something-5468-something.txt
something-6200-something.txt
Is there an easy way to achieve this?

What about on the fly moving files by doing this:
for i in `cat you-file.txt`; do
find . -iname "*-$i-*" -exec mv '{}' /target/dir \;
; done
For every line in your text file, the find command will try to find only does matching the pattern *-$i-* (something-6200-something.txt) and move it to your target dir.

Naive implementation: for file in $(ls); do grep $(echo -n $file | sed -nr 's/[^-]*-([0-9]+).*/\1/p') my-one-txt.txt && mv $file /tmp/somewhere; done
In English: For every file in output of ls: parse number part of filename with sed and grep for it in your text file. grep returns a non-zero exit code if nothing is found, so mv is in evaluated in that case.

Script file named move (executable):
#!/bin/bash
TARGETDIR="$1"
FILES=`find . -type f` # build list of files
while read n # read numbers from standard input
do # n contains a number => filter list of files by that number:
echo "$FILES" | grep "\-$n-" | while read f
do # move file that passed the filter because its name matches n:
mv "$f" "$TARGETDIR"
done
done
Use it like this:
cd directory-with-files
./move target-directory < number-list.txt

Here's a crazy bit of bash hackery
shopt -s extglob nullglob
mv -t /target/dir *-#($(paste -sd "|" numbers.txt))-*
That uses paste to join all the lines in your numbers file with pipe characters, then uses bash extended pattern matching to find the files matching any one of the numbers.
I assume mv from GNU coreutils for the -t option.

Related

Batch Renaming files to a sequence [duplicate]

I want to rename the files in a directory to sequential numbers. Based on creation date of the files.
For Example sadf.jpg to 0001.jpg, wrjr3.jpg to 0002.jpg and so on, the number of leading zeroes depending on the total amount of files (no need for extra zeroes if not needed).
Beauty in one line:
ls -v | cat -n | while read n f; do mv -n "$f" "$n.ext"; done
You can change .ext with .png, .jpg, etc.
Try to use a loop, let, and printf for the padding:
a=1
for i in *.jpg; do
new=$(printf "%04d.jpg" "$a") #04 pad to length of 4
mv -i -- "$i" "$new"
let a=a+1
done
using the -i flag prevents automatically overwriting existing files, and using -- prevents mv from interpreting filenames with dashes as options.
I like gauteh's solution for its simplicity, but it has an important drawback. When running on thousands of files, you can get "argument list too long" message (more on this), and second, the script can get really slow. In my case, running it on roughly 36.000 files, script moved approx. one item per second! I'm not really sure why this happens, but the rule I got from colleagues was "find is your friend".
find -name '*.jpg' | # find jpegs
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | # build mv command
bash # run that command
To count items and build command, gawk was used. Note the main difference, though. By default find searches for files in current directory and its subdirectories, so be sure to limit the search on current directory only, if necessary (use man find to see how).
A very simple bash one liner that keeps the original extensions, adds leading zeros, and also works in OSX:
num=0; for i in *; do mv "$i" "$(printf '%04d' $num).${i#*.}"; ((num++)); done
Simplified version of http://ubuntuforums.org/showthread.php?t=1355021
using Pero's solution on OSX required some modification. I used:
find . -name '*.jpg' \
| awk 'BEGIN{ a=0 }{ printf "mv \"%s\" %04d.jpg\n", $0, a++ }' \
| bash
note: the backslashes are there for line continuation
edit July 20, 2015:
incorporated #klaustopher's feedback to quote the \"%s\" argument of the mv command in order to support filenames with spaces.
with "rename" command
rename -N 0001 -X 's/.*/$N/' *.jpg
or
rename -N 0001 's/.*/$N.jpg/' *.jpg
To work in all situations, put a \" for files that have space in the name
find . -name '*.jpg' | gawk 'BEGIN{ a=1 }{ printf "mv \"%s\" %04d.jpg\n", $0, a++ }' | bash
On OSX, install the rename script from Homebrew:
brew install rename
Then you can do it really ridiculously easily:
rename -e 's/.*/$N.jpg/' *.jpg
Or to add a nice prefix:
rename -e 's/.*/photo-$N.jpg/' *.jpg
NOTE The rename commands here include -n which previews the rename. To actually perform the renaming, remove the -n
If your rename doesn't support -N, you can do something like this:
ls -1 --color=never -c | xargs rename -n 's/.*/our $i; sprintf("%04d.jpg", $i++)/e'
NOTE The rename commands here includes -n which previews the rename. To actually perform the renaming, remove the -n
Edit To start with a given number, you can use the (somewhat ugly-looking) code below, just replace 123 with the number you want:
ls -1 --color=never -c | xargs rename -n 's/.*/our $i; if(!$i) { $i=123; } sprintf("%04d.jpg", $i++)/e'
This lists files in order by creation time (newest first, add -r to ls to reverse sort), then sends this list of files to rename. Rename uses perl code in the regex to format and increment counter.
However, if you're dealing with JPEG images with EXIF information, I'd recommend exiftool
This is from the exiftool documentation, under "Renaming Examples"
exiftool '-FileName<CreateDate' -d %Y%m%d_%H%M%S%%-c.%%e dir
Rename all images in "dir" according to the "CreateDate" date and time, adding a copy number with leading '-' if the file already exists ("%-c"), and
preserving the original file extension (%e). Note the extra '%' necessary to escape the filename codes (%c and %e) in the date format string.
Follow command rename all files to sequence and also lowercase extension:
rename --counter-format 000001 --lower-case --keep-extension --expr='$_ = "$N" if #EXT' *
find . | grep 'avi' | nl -nrz -w3 -v1 | while read n f; do mv "$f" "$n.avi"; done
find . will display all file in folder and subfolders.
grep 'avi' will filter all files with avi extension.
nl -nrz -w3 -v1 will display sequence number starting 001 002 etc following by file name.
while read n f; do mv "$f" "$n.avi"; done will change file name to sequence numbers.
I spent 3-4 hours developing this solution for an article on this:
https://www.cloudsavvyit.com/8254/how-to-bulk-rename-files-to-numeric-file-names-in-linux/
if [ ! -r _e -a ! -r _c ]; then echo 'pdf' > _e; echo 1 > _c ;find . -name "*.$(cat _e)" -print0 | xargs -0 -t -I{} bash -c 'mv -n "{}" $(cat _c).$(cat _e);echo $[ $(cat _c) + 1 ] > _c'; rm -f _e _c; fi
This works for any type of filename (spaces, special chars) by using correct \0 escaping by both find and xargs, and you can set a start file naming offset by increasing echo 1 to any other number if you like.
Set extension at start (pdf in example here). It will also not overwrite any existing files.
Let us assume we have these files in a directory, listed in order of creation, the first being the oldest:
a.jpg
b.JPG
c.jpeg
d.tar.gz
e
then ls -1cr outputs exactly the list above. You can then use rename:
ls -1cr | xargs rename -n 's/^[^\.]*(\..*)?$/our $i; sprintf("%03d$1", $i++)/e'
which outputs
rename(a.jpg, 000.jpg)
rename(b.JPG, 001.JPG)
rename(c.jpeg, 002.jpeg)
rename(d.tar.gz, 003.tar.gz)
Use of uninitialized value $1 in concatenation (.) or string at (eval 4) line 1.
rename(e, 004)
The warning ”use of uninitialized value […]” is displayed for files without an extension; you can ignore it.
Remove -n from the rename command to actually apply the renaming.
This answer is inspired by Luke’s answer of April 2014. It ignores Gnutt’s requirement of setting the number of leading zeroes depending on the total amount of files.
I had a similar issue and wrote a shell script for that reason. I've decided to post it regardless that many good answers were already posted because I think it can be helpful for someone. Feel free to improve it!
numerate
#Gnutt The behavior you want can be achieved by typing the following:
./numerate.sh -d <path to directory> -o modtime -L 4 -b <startnumber> -r
If the option -r is left out the reaming will be only simulated (Should be helpful for testing).
The otion L describes the length of the target number (which will be filled with leading zeros)
it is also possible to add a prefix/suffix with the options -p <prefix> -s <suffix>.
In case somebody wants the files to be sorted numerically before they get numbered, just remove the -o modtime option.
a=1
for i in *.jpg; do
mv -- "$i" "$a.jpg"
a=`expr $a + 1`
done
Again using Pero's solution with little modifying, because find will be traversing the directory tree in the order items are stored within the directory entries. This will (mostly) be consistent from run to run, on the same machine and will essentially be "file/directory creation order" if there have been no deletes.
However, in some case you need to get some logical order, say, by name, which is used in this example.
find -name '*.jpg' | sort -n | # find jpegs
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | # build mv command
bash # run that command
The majority of the other solutions will overwrite existing files already named as a number. This is particularly a problem if running the script, adding more files, and then running the script again.
This script renames existing numerical files first:
#!/usr/bin/perl
use strict;
use warnings;
use File::Temp qw/tempfile/;
my $dir = $ARGV[0]
or die "Please specify directory as first argument";
opendir(my $dh, $dir) or die "can't opendir $dir: $!";
# First rename any files that are already numeric
while (my #files = grep { /^[0-9]+(\..*)?$/ } readdir($dh))
{
for my $old (#files) {
my $ext = $old =~ /(\.[^.]+)$/ ? $1 : '';
my ($fh, $new) = tempfile(DIR => $dir, SUFFIX => $ext);
close $fh;
rename "$dir/$old", $new;
}
}
rewinddir $dh;
my $i;
while (my $file = readdir($dh))
{
next if $file =~ /\A\.\.?\z/;
my $ext = $file =~ /(\.[^.]+)$/ ? $1 : '';
rename "$dir/$file", sprintf("%s/%04d%s", $dir, ++$i, $ext);
}
Sorted by time, limited to jpg, leading zeroes and a basename (in case you likely want one):
ls -t *.jpg | cat -n | \
while read n f; do mv "$f" "$(printf thumb_%04d.jpg $n)"; done
(all on one line, without the \)
Not related to creation date but numbered based on sorted names:
python3 -c \
'ext="jpg"
start_num=0
pad=4
import os,glob
files=glob.glob(f"*.{ext}")
files.sort()
renames=list(zip(files,range(start_num,len(files)+start_num)))
for r in renames:
oname=r[0]
nname=f"{r[1]:0{pad}}.{ext}"
print(oname,"->",nname)
os.rename(oname,nname)
'
This script will sort the files by creation date on Mac OS bash. I use it to mass rename videos. Just change the extension and the first part of the name.
ls -trU *.mp4| awk 'BEGIN{ a=0 }{ printf "mv %s lecture_%03d.mp4\n", $0, a++ }' | bash
ls -1tr | rename -vn 's/.*/our $i;if(!$i){$i=1;} sprintf("%04d.jpg", $i++)/e'
rename -vn - remove n for off test mode
{$i=1;} - control start number
"%04d.jpg" - control count zero 04 and set output extension .jpg
To me this combination of answers worked perfectly:
ls -v | gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | bash
ls -v helps with ordering 1 10 9 in correct: 1 9 10 order, avoiding filename extension problems with jpg JPG jpeg
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' renumbers with 4 characters and leading zeros. By avoiding mv I do not accidentally try to overwrite anything that is there already by accidentally having the same number.
bash executes
Be aware of what #xhienne said, piping unknown content to bash is a security risk. But this was not the case for me as I was using my scanned photos.
Here is what worked for me.
I Have used rename command so that if any file contains spaces in name of it then , mv command dont get confused between spaces and actual file.
Here i replaced spaces , ' ' in a file name with '_' for all jpg files
#! /bin/bash
rename 'y/ /_/' *jpg #replacing spaces with _
let x=0;
for i in *.jpg;do
let x=(x+1)
mv $i $x.jpg
done
Nowadays there is an option after you select multiple files for renaming (I have seen in thunar file manager).
select multiple files
check options
select rename
A prompt comes with all files in that particular dir
just check with the category section
Using sed :
ls -tr | sed "s/(.*)/mv '\1' \=printf('%04s',line('.').jpg)/" > rename.sh
bash rename.sh
This way you can check the script before executing it to avoid big mistakes
Here a another solution with "rename" command:
find -name 'access.log.*.gz' | sort -Vr | rename 's/(\d+)/$1+1/ge'
Pero's answer got me here :)
I wanted to rename files relative to time as the image viewers did not display images in time order.
ls -tr *.jpg | # list jpegs relative to time
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | # build mv command
bash # run that command
To renumber 6000, files in one folder you could use the 'Rename' option of the ACDsee program.
For defining a prefix use this format: ####"*"
Then set the start number and press Rename and the program will rename all 6000 files with sequential numbers.

Add suffix to all files in the directory with an extension

How to add a suffix to all files in the current directory in bash?
Here is what I've tried, but it keeps adding an extra .png to the filename.
for file in *.png; do mv "$file" "${file}_3.6.14.png"; done
for file in *.png; do
mv "$file" "${file%.png}_3.6.14.png"
done
${file%.png} expands to ${file} with the .png suffix removed.
You could do this through rename command,
rename 's/\.png/_3.6.14.png/' *.png
Through bash,
for i in *.png; do mv "$i" "${i%.*}_3.6.14.png"; done
It replaces .png in all the .png files with _3.6.14.png.
${i%.*} Anything after last dot would be cutdown. So .png part would be cutoff from the filename.
mv $i ${i%.*}_3.6.14.png Rename original .png files with the filename+_3.6.14.png.
If you are familiar with regular expressions sed is quite nice.
a) modify the regular expression to your liking and inspect the output
ls | sed -E "s/(.*)\.png$/\1_foo\.png/
b) add the p flag, so that sed provides you the old and new paths. Feed this to xargs with -n2, meaning that it should keep the pairing of 2 arguments.
ls | sed -E "p;s/(.*)\.png/\1_foo\.png/" | xargs -n2 mv
If you know how to rename a single file to your liking programmatically
fname=myfile.png
mv $fname ${fname%.png}_extended.png
you can batch apply this command with xargs:
find -name "*.png" | xargs -n1 bash -c 'mv $0 ${0%.png}_extended.png'
Explanation
We pipe the list of files to xargs and tell it to process one line at a time with the -n1 flag. We then tell xargs to call bash on each instance and provide it with the code to execute via the -c flag.
The $0 references the first input argument the bash receives.
If you need other string substitutions than ${0%.png} there are many cheat sheets such as https://devhints.io/bash.
For more complex substitutions you provide multiple arguments using -n2; these can be collected with $0, $1, etc..
This use of piping + xargs + bash -c is fairly general.
In the short example above, beware that I assumed proper file names (without special characters).

bash removing part of a file name

I have the following files in the following format:
$ ls CombinedReports_LLL-*'('*.csv
CombinedReports_LLL-20140211144020(Untitled_1).csv
CombinedReports_LLL-20140211144020(Untitled_11).csv
CombinedReports_LLL-20140211144020(Untitled_110).csv
CombinedReports_LLL-20140211144020(Untitled_111).csv
CombinedReports_LLL-20140211144020(Untitled_12).csv
CombinedReports_LLL-20140211144020(Untitled_13).csv
CombinedReports_LLL-20140211144020(Untitled_14).csv
CombinedReports_LLL-20140211144020(Untitled_15).csv
CombinedReports_LLL-20140211144020(Untitled_16).csv
CombinedReports_LLL-20140211144020(Untitled_17).csv
CombinedReports_LLL-20140211144020(Untitled_18).csv
CombinedReports_LLL-20140211144020(Untitled_19).csv
I would like this part removed:
20140211144020 (this is the timestamp the reports were run so this will vary)
and end up with something like:
CombinedReports_LLL-(Untitled_1).csv
CombinedReports_LLL-(Untitled_11).csv
CombinedReports_LLL-(Untitled_110).csv
CombinedReports_LLL-(Untitled_111).csv
CombinedReports_LLL-(Untitled_12).csv
CombinedReports_LLL-(Untitled_13).csv
CombinedReports_LLL-(Untitled_14).csv
CombinedReports_LLL-(Untitled_15).csv
CombinedReports_LLL-(Untitled_16).csv
CombinedReports_LLL-(Untitled_17).csv
CombinedReports_LLL-(Untitled_18).csv
CombinedReports_LLL-(Untitled_19).csv
I was thinking simply along the lines of the mv command, maybe something like this:
$ ls CombinedReports_LLL-*'('*.csv
but maybe a sed command or other would be better
rename is part of the perl package. It renames files according to perl-style regular expressions. To remove the dates from your file names:
rename 's/[0-9]{14}//' CombinedReports_LLL-*.csv
If rename is not available, sed+shell can be used:
for fname in Combined*.csv ; do mv "$fname" "$(echo "$fname" | sed -r 's/[0-9]{14}//')" ; done
The above loops over each of your files. For each file, it performs a mv command: mv "$fname" "$(echo "$fname" | sed -r 's/[0-9]{14}//')" where, in this case, sed is able to use the same regular expression as the rename command above. s/[0-9]{14}// tells sed to look for 14 digits in a row and replace them with an empty string.
Without using an other tools like rename or sed and sticking strictly to bash alone:
for f in CombinedReports_LLL-*.csv
do
newName=${f/LLL-*\(/LLL-(}
mv -i "$f" "$newName"
done
for f in CombinedReports_LLL-* ; do
b=${f:0:20}${f:34:500}
mv "$f" "$b"
done
You can try line by line on shell:
f="CombinedReports_LLL-20140211144020(Untitled_11).csv"
b=${f:0:20}${f:34:500}
echo $b
You can use the rename utility for this. It uses syntax much like sed to change filenames. The following example (from the rename man-page) shows how to remove the trailing '.bak' extension from a list of backup files in the local directory:
rename 's/\.bak$//' *.bak
I'm using the advice given in the top response and have put the following line into a shell script:
ls *.nii | xargs rename 's/[f_]{2}//' f_0*.nii
In terminal, this line works perfectly, but in my script it will not execute and reads * as a literal part of the file name.

Rename Files to original extensions

Need help on writing a bash script that will rename files that are being outputted as file name.suffix.date I need these files to be rewritten as name.date.suffix instead.
Edited:
Changed suffix from date to ~
Here's what I have so far:
find . -type f -name "*.~" -print0 | while read -d $'\0' f
do
new=`echo "$f" | sed -e "s/~//"`
mv "$f" "$new"
done
This changes the suffix back to original but can't figure out how to get the date to be named before the extension (fname??)
You can use regular expression matching to pull apart the original file name:
find . -type f -name "*.~" -print0 | while read -d $'\0' f
do
dir=${f%/*}
fname=${f##*/}
[[ $fname =~ (.+)\.([^.]+)\.([^.]+)\.~$ ]] || continue
name=${BASH_REMATCH[1]}
suffix=${BASH_REMATCH[2]}
d=${BASH_REMATCH[3]}
mv "$f" "$dir/$name.$d.$suffix"
done
Bash-only solution:
while IFS=. read -r -u 9 -d '' name suffix date tilde
do
mv "${name}.${suffix}.${date}.~" "${name}.${date}.${suffix}"
done 9< <(find . -type f -name "*.~" -print0)
Notes:
-d '' gives you the same result as -d $'\0'
Splits file names by the dots while reading them. Of course this means it would break if there are dots anywhere else.
Should otherwise work with pretty much any filenames, including those containing space, newlines and other funny business.
create a list of the files first and redirect to a file.
ls > fileList.txt
Open the file and read line by line in Perl. Use a regex to match the parts of the files and capture them like this
my ($fileName,$suffix,$date)=($WholeFileName=~/(.*)\.(.*)\.(.*)/);
This should capture the three seperate variables for you. Now all you need to do is move the old file to the new file name. The new file name will be a concatenation of the above three variables that you have got. $newFileName=$fileName. ".".$date.".".$suffix. If you have a sample fileName post a comment and I can reply with a short script. Perl is not the only way. You could just use bash or awk and find alternate ways to do this.
cut each part of your filenames:
FIN=$(echo test.12345.ABCDEF | sed -e 's/[a-zA-Z0-9]*[\\.][a-zA-Z0-9]*[\\.]//')
DEBUT=$(echo test.12345.ABCDEF | sed -e 's/[\\.][a-zA-Z0-9]*[\\.][a-zA-Z0-9]*//')
MILIEU=$(echo test.12345.ABCDEF | sed -e 's/'${FIN}'//' -e 's/'${DEBUT}'//' -e 's/[\.]*//g')
paste each part as expected:
echo ${DEBUT}.${FIN}.${MILIEU}
rename --no-act 's/\(name-regex\).\(suffix-regex\).\(date-regex\)/\1.\3.\2' *
Tweak the three regexes to fit your file names, and remove --no-act when you're happy with the result to actually rename the files.

Renaming files in a folder to sequential numbers

I want to rename the files in a directory to sequential numbers. Based on creation date of the files.
For Example sadf.jpg to 0001.jpg, wrjr3.jpg to 0002.jpg and so on, the number of leading zeroes depending on the total amount of files (no need for extra zeroes if not needed).
Beauty in one line:
ls -v | cat -n | while read n f; do mv -n "$f" "$n.ext"; done
You can change .ext with .png, .jpg, etc.
Try to use a loop, let, and printf for the padding:
a=1
for i in *.jpg; do
new=$(printf "%04d.jpg" "$a") #04 pad to length of 4
mv -i -- "$i" "$new"
let a=a+1
done
using the -i flag prevents automatically overwriting existing files, and using -- prevents mv from interpreting filenames with dashes as options.
I like gauteh's solution for its simplicity, but it has an important drawback. When running on thousands of files, you can get "argument list too long" message (more on this), and second, the script can get really slow. In my case, running it on roughly 36.000 files, script moved approx. one item per second! I'm not really sure why this happens, but the rule I got from colleagues was "find is your friend".
find -name '*.jpg' | # find jpegs
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | # build mv command
bash # run that command
To count items and build command, gawk was used. Note the main difference, though. By default find searches for files in current directory and its subdirectories, so be sure to limit the search on current directory only, if necessary (use man find to see how).
A very simple bash one liner that keeps the original extensions, adds leading zeros, and also works in OSX:
num=0; for i in *; do mv "$i" "$(printf '%04d' $num).${i#*.}"; ((num++)); done
Simplified version of http://ubuntuforums.org/showthread.php?t=1355021
using Pero's solution on OSX required some modification. I used:
find . -name '*.jpg' \
| awk 'BEGIN{ a=0 }{ printf "mv \"%s\" %04d.jpg\n", $0, a++ }' \
| bash
note: the backslashes are there for line continuation
edit July 20, 2015:
incorporated #klaustopher's feedback to quote the \"%s\" argument of the mv command in order to support filenames with spaces.
with "rename" command
rename -N 0001 -X 's/.*/$N/' *.jpg
or
rename -N 0001 's/.*/$N.jpg/' *.jpg
To work in all situations, put a \" for files that have space in the name
find . -name '*.jpg' | gawk 'BEGIN{ a=1 }{ printf "mv \"%s\" %04d.jpg\n", $0, a++ }' | bash
On OSX, install the rename script from Homebrew:
brew install rename
Then you can do it really ridiculously easily:
rename -e 's/.*/$N.jpg/' *.jpg
Or to add a nice prefix:
rename -e 's/.*/photo-$N.jpg/' *.jpg
NOTE The rename commands here include -n which previews the rename. To actually perform the renaming, remove the -n
If your rename doesn't support -N, you can do something like this:
ls -1 --color=never -c | xargs rename -n 's/.*/our $i; sprintf("%04d.jpg", $i++)/e'
NOTE The rename commands here includes -n which previews the rename. To actually perform the renaming, remove the -n
Edit To start with a given number, you can use the (somewhat ugly-looking) code below, just replace 123 with the number you want:
ls -1 --color=never -c | xargs rename -n 's/.*/our $i; if(!$i) { $i=123; } sprintf("%04d.jpg", $i++)/e'
This lists files in order by creation time (newest first, add -r to ls to reverse sort), then sends this list of files to rename. Rename uses perl code in the regex to format and increment counter.
However, if you're dealing with JPEG images with EXIF information, I'd recommend exiftool
This is from the exiftool documentation, under "Renaming Examples"
exiftool '-FileName<CreateDate' -d %Y%m%d_%H%M%S%%-c.%%e dir
Rename all images in "dir" according to the "CreateDate" date and time, adding a copy number with leading '-' if the file already exists ("%-c"), and
preserving the original file extension (%e). Note the extra '%' necessary to escape the filename codes (%c and %e) in the date format string.
Follow command rename all files to sequence and also lowercase extension:
rename --counter-format 000001 --lower-case --keep-extension --expr='$_ = "$N" if #EXT' *
find . | grep 'avi' | nl -nrz -w3 -v1 | while read n f; do mv "$f" "$n.avi"; done
find . will display all file in folder and subfolders.
grep 'avi' will filter all files with avi extension.
nl -nrz -w3 -v1 will display sequence number starting 001 002 etc following by file name.
while read n f; do mv "$f" "$n.avi"; done will change file name to sequence numbers.
I spent 3-4 hours developing this solution for an article on this:
https://www.cloudsavvyit.com/8254/how-to-bulk-rename-files-to-numeric-file-names-in-linux/
if [ ! -r _e -a ! -r _c ]; then echo 'pdf' > _e; echo 1 > _c ;find . -name "*.$(cat _e)" -print0 | xargs -0 -t -I{} bash -c 'mv -n "{}" $(cat _c).$(cat _e);echo $[ $(cat _c) + 1 ] > _c'; rm -f _e _c; fi
This works for any type of filename (spaces, special chars) by using correct \0 escaping by both find and xargs, and you can set a start file naming offset by increasing echo 1 to any other number if you like.
Set extension at start (pdf in example here). It will also not overwrite any existing files.
Let us assume we have these files in a directory, listed in order of creation, the first being the oldest:
a.jpg
b.JPG
c.jpeg
d.tar.gz
e
then ls -1cr outputs exactly the list above. You can then use rename:
ls -1cr | xargs rename -n 's/^[^\.]*(\..*)?$/our $i; sprintf("%03d$1", $i++)/e'
which outputs
rename(a.jpg, 000.jpg)
rename(b.JPG, 001.JPG)
rename(c.jpeg, 002.jpeg)
rename(d.tar.gz, 003.tar.gz)
Use of uninitialized value $1 in concatenation (.) or string at (eval 4) line 1.
rename(e, 004)
The warning ”use of uninitialized value […]” is displayed for files without an extension; you can ignore it.
Remove -n from the rename command to actually apply the renaming.
This answer is inspired by Luke’s answer of April 2014. It ignores Gnutt’s requirement of setting the number of leading zeroes depending on the total amount of files.
I had a similar issue and wrote a shell script for that reason. I've decided to post it regardless that many good answers were already posted because I think it can be helpful for someone. Feel free to improve it!
numerate
#Gnutt The behavior you want can be achieved by typing the following:
./numerate.sh -d <path to directory> -o modtime -L 4 -b <startnumber> -r
If the option -r is left out the reaming will be only simulated (Should be helpful for testing).
The otion L describes the length of the target number (which will be filled with leading zeros)
it is also possible to add a prefix/suffix with the options -p <prefix> -s <suffix>.
In case somebody wants the files to be sorted numerically before they get numbered, just remove the -o modtime option.
a=1
for i in *.jpg; do
mv -- "$i" "$a.jpg"
a=`expr $a + 1`
done
Again using Pero's solution with little modifying, because find will be traversing the directory tree in the order items are stored within the directory entries. This will (mostly) be consistent from run to run, on the same machine and will essentially be "file/directory creation order" if there have been no deletes.
However, in some case you need to get some logical order, say, by name, which is used in this example.
find -name '*.jpg' | sort -n | # find jpegs
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | # build mv command
bash # run that command
The majority of the other solutions will overwrite existing files already named as a number. This is particularly a problem if running the script, adding more files, and then running the script again.
This script renames existing numerical files first:
#!/usr/bin/perl
use strict;
use warnings;
use File::Temp qw/tempfile/;
my $dir = $ARGV[0]
or die "Please specify directory as first argument";
opendir(my $dh, $dir) or die "can't opendir $dir: $!";
# First rename any files that are already numeric
while (my #files = grep { /^[0-9]+(\..*)?$/ } readdir($dh))
{
for my $old (#files) {
my $ext = $old =~ /(\.[^.]+)$/ ? $1 : '';
my ($fh, $new) = tempfile(DIR => $dir, SUFFIX => $ext);
close $fh;
rename "$dir/$old", $new;
}
}
rewinddir $dh;
my $i;
while (my $file = readdir($dh))
{
next if $file =~ /\A\.\.?\z/;
my $ext = $file =~ /(\.[^.]+)$/ ? $1 : '';
rename "$dir/$file", sprintf("%s/%04d%s", $dir, ++$i, $ext);
}
Sorted by time, limited to jpg, leading zeroes and a basename (in case you likely want one):
ls -t *.jpg | cat -n | \
while read n f; do mv "$f" "$(printf thumb_%04d.jpg $n)"; done
(all on one line, without the \)
Not related to creation date but numbered based on sorted names:
python3 -c \
'ext="jpg"
start_num=0
pad=4
import os,glob
files=glob.glob(f"*.{ext}")
files.sort()
renames=list(zip(files,range(start_num,len(files)+start_num)))
for r in renames:
oname=r[0]
nname=f"{r[1]:0{pad}}.{ext}"
print(oname,"->",nname)
os.rename(oname,nname)
'
This script will sort the files by creation date on Mac OS bash. I use it to mass rename videos. Just change the extension and the first part of the name.
ls -trU *.mp4| awk 'BEGIN{ a=0 }{ printf "mv %s lecture_%03d.mp4\n", $0, a++ }' | bash
ls -1tr | rename -vn 's/.*/our $i;if(!$i){$i=1;} sprintf("%04d.jpg", $i++)/e'
rename -vn - remove n for off test mode
{$i=1;} - control start number
"%04d.jpg" - control count zero 04 and set output extension .jpg
To me this combination of answers worked perfectly:
ls -v | gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | bash
ls -v helps with ordering 1 10 9 in correct: 1 9 10 order, avoiding filename extension problems with jpg JPG jpeg
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' renumbers with 4 characters and leading zeros. By avoiding mv I do not accidentally try to overwrite anything that is there already by accidentally having the same number.
bash executes
Be aware of what #xhienne said, piping unknown content to bash is a security risk. But this was not the case for me as I was using my scanned photos.
Here is what worked for me.
I Have used rename command so that if any file contains spaces in name of it then , mv command dont get confused between spaces and actual file.
Here i replaced spaces , ' ' in a file name with '_' for all jpg files
#! /bin/bash
rename 'y/ /_/' *jpg #replacing spaces with _
let x=0;
for i in *.jpg;do
let x=(x+1)
mv $i $x.jpg
done
Nowadays there is an option after you select multiple files for renaming (I have seen in thunar file manager).
select multiple files
check options
select rename
A prompt comes with all files in that particular dir
just check with the category section
Using sed :
ls -tr | sed "s/(.*)/mv '\1' \=printf('%04s',line('.').jpg)/" > rename.sh
bash rename.sh
This way you can check the script before executing it to avoid big mistakes
Here a another solution with "rename" command:
find -name 'access.log.*.gz' | sort -Vr | rename 's/(\d+)/$1+1/ge'
Pero's answer got me here :)
I wanted to rename files relative to time as the image viewers did not display images in time order.
ls -tr *.jpg | # list jpegs relative to time
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.jpg\n", $0, a++ }' | # build mv command
bash # run that command
To renumber 6000, files in one folder you could use the 'Rename' option of the ACDsee program.
For defining a prefix use this format: ####"*"
Then set the start number and press Rename and the program will rename all 6000 files with sequential numbers.

Resources