There are multiple files in a directory that begin with prefix fgh
, for example:
fghfilea
fghfileb
fghfilec
I want to rename all of them to begin with prefix jkl
. Is there a single command to do that instead of renaming each file individually?
This question is related to
file
unix
rename
batch-rename
Using find
, xargs
and sed
:
find . -name "fgh*" -type f -print0 | xargs -0 -I {} sh -c 'mv "{}" "$(dirname "{}")/`echo $(basename "{}") | sed 's/^fgh/jkl/g'`"'
It's more complex than @nik's solution but it allows to rename files recursively. For instance, the structure,
.
+-- fghdir
¦ +-- fdhfilea
¦ +-- fghfilea
+-- fghfile\ e
+-- fghfilea
+-- fghfileb
+-- fghfilec
+-- other
+-- fghfile\ e
+-- fghfilea
+-- fghfileb
+-- fghfilec
would be transformed to this,
.
+-- fghdir
¦ +-- fdhfilea
¦ +-- jklfilea
+-- jklfile\ e
+-- jklfilea
+-- jklfileb
+-- jklfilec
+-- other
+-- jklfile\ e
+-- jklfilea
+-- jklfileb
+-- jklfilec
The key to make it work with xargs
is to invoke the shell from xargs.
rename might not be in every system. so if you don't have it, use the shell this example in bash shell
for f in fgh*; do mv "$f" "${f/fgh/xxx}";done
My version of renaming mass files:
for i in *; do
echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh
A generic script to run a sed
expression on a list of files (combines the sed
solution with the rename
solution):
#!/bin/sh
e=$1
shift
for f in $*; do
fNew=$(echo "$f" | sed "$e")
mv "$f" "$fNew";
done
Invoke by passing the script a sed
expression, and then any list of files, just like a version of rename
:
script.sh 's/^fgh/jkl/' fgh*
Here's a way to do it using command-line Groovy:
groovy -e 'new File(".").eachFileMatch(~/fgh.*/) {it.renameTo(it.name.replaceFirst("fgh", "jkl"))}'
There are many ways to do it (not all of these will work on all unixy systems):
ls | cut -c4- | xargs -I§ mv fgh§ jkl§
The § may be replaced by anything you find convenient. You could do this with find -exec
too but that behaves subtly different on many systems, so I usually avoid that
for f in fgh*; do mv "$f" "${f/fgh/jkl}";done
Crude but effective as they say
rename 's/^fgh/jkl/' fgh*
Real pretty, but rename is not present on BSD, which is the most common unix system afaik.
rename fgh jkl fgh*
ls | perl -ne 'chomp; next unless -e; $o = $_; s/fgh/jkl/; next if -e; rename $o, $_';
If you insist on using Perl, but there is no rename on your system, you can use this monster.
Some of those are a bit convoluted and the list is far from complete, but you will find what you want here for pretty much all unix systems.
Using renamer:
$ renamer --find /^fgh/ --replace jkl * --dry-run
Remove the --dry-run
flag once you're happy the output looks correct.
I wrote this script to search for all .mkv files recursively renaming found files to .avi. You can customize it to your neeeds. I've added some other things such as getting file directory, extension, file name from a file path just incase you need to refer to something in the future.
find . -type f -name "*.mkv" | while read fp; do
fd=$(dirname "${fp}");
fn=$(basename "${fp}");
ext="${fn##*.}";
f="${fn%.*}";
new_fp="${fd}/${f}.avi"
mv -v "$fp" "$new_fp"
done;
I would recommend using my own script, which solves this problem. It also has options to change the encoding of the file names, and to convert combining diacriticals to precomposed characters, a problem I always have when I copy files from my Mac.
#!/usr/bin/perl
# Copyright (c) 2014 André von Kugland
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
$help_msg =
"rename.pl, a script to rename files in batches, using Perl
expressions to transform their names.
Usage:
rename.pl [options] FILE1 [FILE2 ...]
Where options can be:
-v Verbose.
-vv Very verbose.
--apply Really apply modifications.
-e PERLCODE Execute PERLCODE. (e.g. 's/a/b/g')
--from-charset=CS Source charset. (e.g. \"iso-8859-1\")
--to-charset=CS Destination charset. (e.g. \"utf-8\")
--unicode-normalize=NF Unicode normalization form. (e.g. \"KD\")
--basename Modifies only the last element of the path.
";
use Encode;
use Getopt::Long;
use Unicode::Normalize 'normalize';
use File::Basename;
use I18N::Langinfo qw(langinfo CODESET);
Getopt::Long::Configure ("bundling");
# ----------------------------------------------------------------------------------------------- #
# Our variables. #
# ----------------------------------------------------------------------------------------------- #
my $apply = 0;
my $verbose = 0;
my $help = 0;
my $debug = 0;
my $basename = 0;
my $unicode_normalize = "";
my @scripts;
my $from_charset = "";
my $to_charset = "";
my $codeset = "";
# ----------------------------------------------------------------------------------------------- #
# Get cmdline options. #
# ----------------------------------------------------------------------------------------------- #
$result = GetOptions ("apply" => \$apply,
"verbose|v+" => \$verbose,
"execute|e=s" => \@scripts,
"from-charset=s" => \$from_charset,
"to-charset=s" => \$to_charset,
"unicode-normalize=s" => \$unicode_normalize,
"basename" => \$basename,
"help|h|?" => \$help,
"debug" => \$debug);
# If not going to apply, then be verbose.
if (!$apply && $verbose == 0) {
$verbose = 1;
}
if ((($#scripts == -1)
&& (($from_charset eq "") || ($to_charset eq ""))
&& $unicode_normalize eq "")
|| ($#ARGV == -1) || ($help)) {
print $help_msg;
exit(0);
}
if (($to_charset ne "" && $from_charset eq "")
||($from_charset eq "" && $to_charset ne "")
||($to_charset eq "" && $from_charset eq "" && $unicode_normalize ne "")) {
$codeset = langinfo(CODESET);
$to_charset = $codeset if $from_charset ne "" && $to_charset eq "";
$from_charset = $codeset if $from_charset eq "" && $to_charset ne "";
}
# ----------------------------------------------------------------------------------------------- #
# Composes the filter function using the @scripts array and possibly other options. #
# ----------------------------------------------------------------------------------------------- #
$f = "sub filterfunc() {\n my \$s = shift;\n";
$f .= " my \$d = dirname(\$s);\n my \$s = basename(\$s);\n" if ($basename != 0);
$f .= " for (\$s) {\n";
$f .= " $_;\n" foreach (@scripts); # Get scripts from '-e' opt. #
# Handle charset translation and normalization.
if (($from_charset ne "") && ($to_charset ne "")) {
if ($unicode_normalize eq "") {
$f .= " \$_ = encode(\"$to_charset\", decode(\"$from_charset\", \$_));\n";
} else {
$f .= " \$_ = encode(\"$to_charset\", normalize(\"$unicode_normalize\", decode(\"$from_charset\", \$_)));\n"
}
} elsif (($from_charset ne "") || ($to_charset ne "")) {
die "You can't use `from-charset' nor `to-charset' alone";
} elsif ($unicode_normalize ne "") {
$f .= " \$_ = encode(\"$codeset\", normalize(\"$unicode_normalize\", decode(\"$codeset\", \$_)));\n"
}
$f .= " }\n";
$f .= " \$s = \$d . '/' . \$s;\n" if ($basename != 0);
$f .= " return \$s;\n}\n";
print "Generated function:\n\n$f" if ($debug);
# ----------------------------------------------------------------------------------------------- #
# Evaluates the filter function body, so to define it in our scope. #
# ----------------------------------------------------------------------------------------------- #
eval $f;
# ----------------------------------------------------------------------------------------------- #
# Main loop, which passes names through filters and renames files. #
# ----------------------------------------------------------------------------------------------- #
foreach (@ARGV) {
$old_name = $_;
$new_name = filterfunc($_);
if ($old_name ne $new_name) {
if (!$apply or (rename $old_name, $new_name)) {
print "`$old_name' => `$new_name'\n" if ($verbose);
} else {
print "Cannot rename `$old_name' to `$new_name'.\n";
}
} else {
print "`$old_name' unchanged.\n" if ($verbose > 1);
}
}
On Solaris you can try:
for file in `find ./ -name "*TextForRename*"`; do
mv -f "$file" "${file/TextForRename/NewText}"
done
Another possible parameter expansion:
for f in fgh*; do mv -- "$f" "jkl${f:3}"; done
rename fgh jkl fgh*
To install the Perl rename script:
sudo cpan install File::Rename
There are two renames as mentioned in the comments in Stephan202's answer.
Debian based distros have the Perl rename. Redhat/rpm distros have the C rename.
OS X doesn't have one installed by default (at least in 10.8), neither does Windows/Cygwin.
Using StringSolver tools (windows & Linux bash) which process by examples:
filter fghfilea ok fghreport ok notfghfile notok; mv --all --filter fghfilea jklfilea
It first computes a filter based on examples, where the input is the file names and the output (ok and notok, arbitrary strings). If filter had the option --auto or was invoked alone after this command, it would create a folder ok
and a folder notok
and push files respectively to them.
Then using the filter, the mv
command is a semi-automatic move which becomes automatic with the modifier --auto. Using the previous filter thanks to --filter, it finds a mapping from fghfilea
to jklfilea
and then applies it on all filtered files.
Other one-line solutions
Other equivalent ways of doing the same (each line is equivalent), so you can choose your favorite way of doing it.
filter fghfilea ok fghreport ok notfghfile notok; mv --filter fghfilea jklfilea; mv
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter fghfilea "mv fghfilea jklfilea"
# Even better, automatically infers the file name
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter "mv fghfilea jklfilea"
Multi-step solution
To carefully find if the commands are performing well, you can type the following:
filter fghfilea ok
filter fghfileb ok
filter fghfileb notok
and when you are confident that the filter is good, perform the first move:
mv fghfilea jklfilea
If you want to test, and use the previous filter, type:
mv --test --filter
If the transformation is not what you wanted (e.g. even with mv --explain
you see that something is wrong), you can type mv --clear
to restart moving files, or add more examples mv input1 input2
where input1 and input2 are other examples
When you are confident, just type
mv --filter
and voilà! All the renaming is done using the filter.
DISCLAIMER: I am a co-author of this work made for academic purposes. There might also be a bash-producing feature soon.
This script worked for me for recursive renaming with directories/file names possibly containing white-spaces:
find . -type f -name "*\;*" | while read fname; do
dirname=`dirname "$fname"`
filename=`basename "$fname"`
newname=`echo "$filename" | sed -e "s/;/ /g"`
mv "${dirname}/$filename" "${dirname}/$newname"
done
Notice the sed
expression which in this example replaces all occurrences of ;
with space . This should of course be replaced according to the specific needs.
#!/bin/sh
#replace all files ended witn .f77 to .f90 in a directory
for filename in *.f77
do
#echo $filename
#b= echo $filename | cut -d. -f1
#echo $b
mv "${filename}" "${filename%.f77}.f90"
done
There are several ways, but using rename
will probably be the easiest.
Using one version of rename
:
rename 's/^fgh/jkl/' fgh*
Using another version of rename
(same as Judy2K's answer):
rename fgh jkl fgh*
You should check your platform's man page to see which of the above applies.
Generic command would be
find /path/to/files -name '<search>*' -exec bash -c 'mv $0 ${0/<search>/<replace>}' {} \;
where <search>
and <replace>
should be replaced with your source and target respectively.
As a more specific example tailored to your problem (should be run from the same folder where your files are), the above command would look like:
find . -name 'gfh*' -exec bash -c 'mv $0 ${0/gfh/jkl}' {} \;
For a "dry run" add echo
before mv
, so that you'd see what commands are generated:
find . -name 'gfh*' -exec bash -c 'echo mv $0 ${0/gfh/jkl}' {} \;
This is how sed
and mv
can be used together to do rename:
for f in fgh*; do mv "$f" $(echo "$f" | sed 's/^fgh/jkl/g'); done
As per comment below, if the file names have spaces in them, quotes may need to surround the sub-function that returns the name to move the files to:
for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
You can also use below script. it is very easy to run on terminal...
//Rename multiple files at a time
for file in FILE_NAME*
do
mv -i "${file}" "${file/FILE_NAME/RENAMED_FILE_NAME}"
done
Example:-
for file in hello*
do
mv -i "${file}" "${file/hello/JAISHREE}"
done
Using mmv:
mmv "fgh*" "jkl#1"
It was much easier (on my Mac) to do this in Ruby. Here are 2 examples:
# for your fgh example. renames all files from "fgh..." to "jkl..."
files = Dir['fgh*']
files.each do |f|
f2 = f.gsub('fgh', 'jkl')
system("mv #{f} #{f2}")
end
# renames all files in directory from "021roman.rb" to "021_roman.rb"
files = Dir['*rb'].select {|f| f =~ /^[0-9]{3}[a-zA-Z]+/}
files.each do |f|
f1 = f.clone
f2 = f.insert(3, '_')
system("mv #{f1} #{f2}")
end
This worked for me using regexp:
I wanted files to be renamed like this:
file0001.txt -> 1.txt
ofile0002.txt -> 2.txt
f_i_l_e0003.txt -> 3.txt
usig the [a-z|_]+0*([0-9]+.) regexp where ([0-9]+.) is a group substring to use on the rename command
ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)/, arr) { print arr[0] " " arr[1] }'|xargs -l mv
Produces:
mv file0001.txt 1.txt
mv ofile0002.txt 2.txt
mv f_i_l_e0003.txt 3.txt
Another example:
file001abc.txt -> abc1.txt
ofile0002abcd.txt -> abcd2.txt
ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)([a-z]+)/, arr) { print arr[0] " " arr[2] arr[1] }'|xargs -l mv
Produces:
mv file001abc.txt abc1.txt
mv ofile0002abcd.txt abcd2.txt
Warning, be careful.
Source: Stackoverflow.com