52

I have a folder with a number of files in it ABC.* (there are roughly 100 such files). I want to duplicate them all to new files with names starting with DEF.*

So, I want

ABC.Page1
ABC.Page2
ABC.Topic12
...etc

copied to

DEF.Page1
DEF.Page2
DEF.Topic12
...etc

What is the simplest way to do this with a batch command (in BASH or similar)? I am thinking something involving sed or awk or xargs, but I'm having difficulty figuring out the syntax. I could write a Python script, but I'm thinking there is probably a command line solution that is not too complicated.

2
  • A venerable mc file manager has a most extensive support for this sort of mass renames.
    – oakad
    Apr 2, 2014 at 0:25
  • @oakad tar is no spring-chicken, either. See my answer for an example.
    – mikeserv
    Apr 2, 2014 at 10:57

6 Answers 6

57

How about something like this in bash:

for file in ABC.*; do cp "$file" "${file/ABC/DEF}";done

you can test it by putting echo in front of the cp command:

for file in ABC.*; do echo cp "$file" "${file/ABC/DEF}";done
0
8

To do this efficiently with a large number of files, it is better to avoid a starting a different cp process for each one. One way would be to copy then rename them using prename (rename is symlinked to this by default on Debian based distros). Using this and the Linux mktemp:

tmp=$(mktemp -d --tmpdir=.)
cp ABC.* "$tmp"
prename "s:$tmp/ABC:DEF:" "$tmp/"*
rmdir "$tmp"

Update

Actually pax may be a better way to go here:

pax -rws '/^ABC/DEF/' ABC.* .

Unfortunately, despite pax being both POSIX and Linux Standard Base, few distros currently include it by default.

1
  • nice touch using the : as the separator to avoid escaping. Apr 2, 2014 at 4:48
5

tar will do this for you really fast.

TEST

First I created 2 directories and 10 files:

% mkdir test1 test2 ; cd test1
% for n in `seq 1 10` ; do touch ABC.file$n ; done
% ls

> ABC.file1   ABC.file2  ABC.file4  ABC.file6  ABC.file8
> ABC.file10  ABC.file3  ABC.file5  ABC.file7  ABC.file9

Then I copied them:

% tar -cf - ./* |\ 
    tar -C../test2 --transform='s/ABC/DEF/' -xf -
% ls ../test2

> DEF.file1   DEF.file2  DEF.file4  DEF.file6  DEF.file8
> DEF.file10  DEF.file3  DEF.file5  DEF.file7  DEF.file9

TRANSFORM

So GNU tar will accept a sed --transform=EXPRESSION for file renaming. This can even rename only some of the files. For instance:

% tar -cf - ./* |\ 
    tar -C../test2 --transform='s/ABC\(.*[0-5]\)/DEF\1/' -xf -
% ls ../test2

> ABC.file6  ABC.file8  DEF.file1   DEF.file2  DEF.file4
> ABC.file7  ABC.file9  DEF.file10  DEF.file3  DEF.file5

So that's one advantage.

STREAM

Also consider that this is only two tar processes - and that will not alter regardless of your file count.

tar | tar

tar is as optimized as you could want it to be. This will never have problem argument counts or runaway child processes. This is just A > B done.

ARGUMENTS

I use 7 distinct arguments combined between my two tar processes here. The most important one is listed here first:

- stdout/stdin - this informs tar that it will be streaming either its input or output to or from stdin/stdout which it will interpret correctly depending on whether or not it is building or extracting an archive.

-c create - this tells tar to build the archive. The next argument tar expects is...

-f file - we specify that tar will be working with a file object rather than a tape-device or whatever. And the file it will be working with, as noted above, is stdin/stdout - in other words, our |pipe.

./* all $PWD/files - not too much to explain here except that the archive argument comes first, so - then ./*.

...and on the other side of the |pipe...

-C change directory - this informs tar that it needs to change to the directory I specify before performing any other action, so effectively it just cd ../test2 before extraction.

--transform='s/ed/EXPR/' - as has already been mentioned, this did the renaming. But the docs indicate that it can take any sed expression or //flag.

-x extract - after tar changes to our target directory and receives our renaming instructions we instruct it to begin extracting all of the files into its current directory from the -f - |pipe archive file. No mystery.

3
  • 1
    Here is the pax solution for this question. I would add it to my answer, but I think it is better suited here - pax -rws '/^ABC/DEF/' ABC.* .
    – Graeme
    Apr 3, 2014 at 2:48
  • @Graeme - woah - that's way better! Man, it beats the hell out of pearl rename, too! You should put this in your answer - pax is POSIX specified - hence the pax.
    – mikeserv
    Apr 3, 2014 at 4:57
  • pax might be POSIX, but it is rarely installed by default unfortunately.
    – Graeme
    Apr 3, 2014 at 11:45
4

If you don't mind using a less standard tool, I recommend using mmv (mass move) which was written for this very kind of tasks. Using mcp (mass copy), which is part of the suite, you could simply do

mcp "ABC.*" "DEF.#1"

It's not exactly easy to find download packages. The current Debian package (which Arch also uses in AUR) to compile at home can be found here.

1
  • installs on ubuntu 15.10 out of the box, apt-get install mmv
    – Thufir
    Feb 14, 2016 at 11:52
2

In zsh, put the following lines in your .zshrc:

autoload -U zmv
alias zmv='noglob zmv'
alias zcp='zmv -C'
alias zln='zmv -L'

Then, on the command line, you can run

zcp 'ABC(.*)' 'DEF$1'

or simply

zcp -W ABC.* DEF.*

In another shell, as long as you have zsh installed:

zsh -c 'autoload zmv; noglob zmv -CW ABC.* DEF.*'
1

You could do this:

for i in ABC.*; do cp {ABC,DEF}."${i#*.}"; done
1
  • How should I read the "${i#*.}" bit?
    – dumbledad
    Nov 9, 2017 at 11:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .