Get index of argument with xargs? - bash

In bash, I have list of files all named the same (in different sub directories) and I want to order them by creation/modified time, something like this:
ls -1t /tmp/tmp-*/my-file.txt | xargs ...
I would like to rename those files with some sort of index or something so I can move them all into the same folder. My result would ideally be something like:
my-file0.txt
my-file1.txt
my-file2.txt
Something like that. How would I go about doing this?

You can just loop through these files and keep appending an incrementing counter to desired file name:
for f in /tmp/tmp-*/my-file.txt; do
fname="${f##*/}"
fname="${fname%.*}"$((i++)).txt
mv "$f" "/dest/dir/$fname"
done
EDIT: In order to sort listed files my modification time as is the case with ls -1t you can use this script:
while IFS= read -d '' -r f; do
f="${f#* }"
fname="${f##*/}"
fname="${fname%.*}"$((i++)).txt
mv "$f" "/dest/dir/$fname"
done < <(find /tmp/tmp-* -name 'my-file.txt' -printf "%T# %p\0" | sort -zk1nr)
This handles filenames with all special characters like white spaces, newlines, glob characters etc since we are ending each filename with NUL or \0 character in -printf option. Note that we are also using sort -z to handle NUL terminated data.

So I found an answer to my own question, thoughts on this one?
ls -1t /tmp/tmp-*/my-file.txt | awk 'BEGIN{ a=0 }{ printf "cp %s /tmp/all-the-files/my-file_%03d.txt\n", $0, a++ }' | bash;
I found this from another stack overflow question looking for something similar that my search didn't find at first. I was impressed with the awk line, thought that was pretty neat.

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.

Rename files named foobar(12345).txt to 12345.txt

All:
Quickly and succinctly, I have many many files named as such:
lorem(12312315).txt
ipsum(578938-12-315-13-416-4).txt
amet(ran-dom-guid).txt
And I want to rename them to what's inside the parentheses dot text, like so:
12312315.txt
578938-12-315-13-416-4.txt
randomguid.txt
I'm sure a mix of sed, awk, grep, etc will do it, but commenting out the parentheses from the shell is throwing me. I cant come up with a string that will do it.
If anyone is kind enough to share a few thought cycles and help me, it would be a lovely Karma gesture!
Thanks for reading!
-Jim
Another flavor:
find . -type f -name \*\(\*\).txt -print0 | xargs -0 sh -c '
for filename ; do
basename_core="${filename##*(}"
basename_core="${basename_core%%)*}"
mv "${filename}" "${basename_core}".txt
done' dummy
This might work for you (GNU sed and shell);
sed -n 's/.*(\(.*\)).*/mv '\''&'\'' '\''\1.txt'\''/p' *.txt
This will print out a list of move commands, after you have validated they are correct, pipe to shell:
sed -n 's/.*(\(.*\)).*/mv '\''&'\'' '\''\1.txt'\''/p' *.txt | sh
find and mv can handle this, with a bash rematch to find your names;
#!/bin/bash
touch lorem\(12312315\).txt
touch ipsum\(578938-12-315-13-416-4\).txt
touch amet\(ran-dom-guid\).txt
pat=".*\((.*)\).txt"
for f in $(find . -type f -name "*.txt" ); do
if [[ $f =~ $pat ]]; then
mv $f ${BASH_REMATCH[1]}.txt
fi
done
ls *.txt
A for loop and Parameter Expansion.
#!/usr/bin/env bash
for f in *\(*\)*.txt; do
temp=${f#*\(}
value=${temp%\)*}
case $value in
*[!0-9-]*) value="${value//-}";;
esac
echo mv -v "$f" "$value.txt"
done
Remove the echo if you're satisfied with the output, so mv can rename/move the files.
Thank you everyone for the responses! I ended up using a mishmash of your suggestions and doing something else entirely, but I'm posting here for posterity...
The files all had one thing in common, the GUID contained in the filename was also always contained in line 2 of the accompanying file, so I yank lane two, strip out the things that are NOT the guid, and rename the file to that string, for any .xml file in the directory where the script is run.
as such:
for i in ./*xml
do
GUID=`cat "$i" | sed -n '2p' | awk '{print $1}' | sed 's/<id>//g' | sed 's/<\/id>//'`
echo File "|" $i "|" is "|" $GUID
done
In the actual script, I do a MV instead of an ECHO and the files are renamed to the guid.
Hopefully this helps someone else in the future, and yes, I know it's wasteful to call sed three times. If I were better with regular expressions, I'm sure I could get that down to one! :)
Thanks!

Handle files with space in filename and output file names

I need to write a Bash script that achieve the following goals:
1) move the newest n pdf files from folder 1 to folder 2;
2) correctly handles files that could have spaces in file names;
3) output each file name in a specific position in a text file. (In my actual usage, I will use sed to put the file names in a specific position of an existing file.)
I tried to make an array of filenames and then move them and do text output in a loop. However, the following array cannot handle files with spaces in filename:
pdfs=($(find -name "$DOWNLOADS/*.pdf" -print0 | xargs -0 ls -1 -t | head -n$NUM))
Suppose a file has name "Filename with Space". What I get from the above array will have "with" and "Space" in separate array entries.
I am not sure how to avoid these words in the same filename being treated separately.
Can someone help me out?
Thanks!
-------------Update------------
Sorry for being vague on the third point as I thought I might be able to figure that out after achieving the first and second goals.
Basically, it is a text file that have a line start with "%comment" near the end and I will need to insert the filenames before that line in the format "file=PATH".
The PATH is the folder 2 that I have my pdfs moved to.
You can achieve this using mapfile in conjunction with gnu versions of find | sort | cut | head that have options to operate on NUL terminated filenames:
mapfile -d '' -t pdfs < <(find "$DOWNLOADS/*.pdf" -name 'file*' -printf '%T#:%p\0' |
sort -z -t : -rnk1 | cut -z -d : -f2- | head -z -n $NUM)
Commands used are:
mapfile -d '': To read array with NUL as delimiter
find: outputs each file's modification stamp in EPOCH + ":" + filename + NUL byte
sort: sorts reverse numerically on 1st field
cut: removes 1st field from output
head: outputs only first $NUM filenames
find downloads -name "*.pdf" -printf "%T# %p\0" |
sort -z -t' ' -k1 -n |
cut -z -d' ' -f2- |
tail -z -n 3
find all *.pdf files in downloads
for each file print it's modifition date %T with the format specifier # that means seconds since epoch with fractional part, then print space, filename and separate with \0
Sort the null separated stream using space as field separator using only first field using numerical sort
Remove the first field from the stream, ie. creation date, leaving only filenames.
Get the count of the newest files, in this example 3 newest files, by using tail. We could also do reverse sort and use head, no difference.
Don't use ls in scripts. ls is for nice formatted output. You could do xargs -0 stat --printf "%Y %n\0" which would basically move your script forward, as ls isn't meant to be used for scripts. Just that I couldn't make stat output fractional part of creation date.
As for the second part, we need to save the null delimetered list to a file
find downloads ........ >"$tmp"
and then:
str='%comment'
{
grep -B$((2**32)) -x "$str" "$out" | grep -v "$str"
# I don't know what you expect to do with newlines in filenames, but I guess you don't have those
cat "$tmp" | sed -z 's/^/file=/' | sed 's/\x0/\n/g'
grep -A$((2**32)) -x "$str" "$out"
} | sponge "$out"
where output is the output file name
assuming output file name is stored in variable "$out"
filter all lines before the %comment and remove the line %comment itself from the file
output each filename with file= on the beginning. I also substituted zeros for newlines.
the filter all lines after %comment including %comment itself
write the output for outfile. Remember to use a temporary file.
Don't use pdf=$(...) on null separated inputs. You can use mapfile to store that to an array, as other answers provided.
Then to move the files, do smth like
<"$tmp" xargs -0 -i mv {} "$outdir"
or faster, with a single move:
{ cat <"$tmp"; printf "%s\0" "$outdir"; } | xargs -0 mv
or alternatively:
<"$tmp" xargs -0 sh -c 'outdir="$1"; shift; mv "$#" "$outdir"' -- "$outdir"
Live example at turorialspoint.
I suppose following code will be close to what you want:
IFS=$'\n' pdfs=($(find -name "$DOWNLOADS/*.pdf" -print0 | xargs -0 -I ls -lt "{}" | tail -n +1 | head -n$NUM))
Then you can access the output through ${pdfs[0]}, ${pdfs[1]}, ...
Explanations
IFS=$'\n' makes the following line to be split only with "\n".
-I option for xargs tells xargs to substitute {} with filenames so it can be quoted as "{}".
tail -n +1 is a trick to suppress an error message saying "xargs: 'ls' terminated by signal 13".
Hope this helps.
Bash v4 has an option globstar, after enabling this option, we can use ** to match zero or more subdirectories.
mapfile is a built-in command, which is used for reading lines into an indexed array variable. -t option removes a trailing newline.
shopt -s globstar
mapfile -t pdffiles < <(ls -t1 **/*.pdf | head -n"$NUM")
typeset -p pdffiles
for f in "${pdffiles[#]}"; do
echo "==="
mv "${f}" /dest/path
sed "/^%comment/i${f}=/dest/path" a-text-file.txt
done

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