3

So there's this rename(1) Perl thing. It suits my task precisely, except that I need it to basically cp files instead of mv.

How to achieve that? I have quite a few rules of renaming, all expressed compactly in s|/foodir/|/|;s|/bardir/|/| form, and then a few lines of file patterns which I need to m̶o̶v̶e̶ copy.

It looks somewhat like this:

rename -v 's|/pars/|/|; s|/fts/|/|; s|innobase/include|include|' \
 storage/{innobase,xtradb}/pars/{pars0grm.cc,pars0grm.y,pars0lex.l,lexyy.cc} \
 storage/{innobase,xtradb}/fts/{fts0blex.cc,fts0blex.l,fts0pars.cc,fts0pars.y,fts0tlex.cc,fts0tlex.l} \
 storage/innobase/include/fts0[bt]lex.h
Rui F Ribeiro
57.9k28 gold badges154 silver badges237 bronze badges
asked Jun 16, 2016 at 9:36

3 Answers 3

3

That's a job for pax. pax is a standard POSIX command; some Linux distributions omit it from the default installation, so you may need to install the package explicitly. You don't get the full power of Perl, just basic sed regex replacement, but that's good enough for your use case.

pax -rw -pe -s'|/pars/|/|' -s'|/fts/|/|' -s'|innobase/include|include|' ...

If you want something more powerful, there's zsh's zmv. There are many expamples on this site, e.g. Traverse, copy & transform file names, how to rename files while copying?

answered Jun 17, 2016 at 0:18
1

Like you, I don't want a different tool or syntax, I want copy with exactly the same syntax as rename! I just answered this in Using regular expressions with cp. Posting here for convenience:

I really like the regex syntax of the rename perl script (by Robin Barker and Larry Wall), e.g.:

rename "s/OldFile/NewFile/" OldFile*

OldFile.c and OldFile.h are renamed to NewFile.c and NewFile.h, respectively

I simply wanted the exact same thing with a copy command:

copy "s/OldFile/NewFile/" OldFile*

So I duplicated that script and changed the rename statement to copy via File::Copy. Et voila! A copy command with perl-regex syntax: jcward/vopy

Paulo Tomé
3,8326 gold badges28 silver badges39 bronze badges
answered Apr 6, 2018 at 17:18
2
  • So... while trying to avoid more new tools, you basically produced a new tool? :) Commented Apr 8, 2018 at 8:28
  • Ha ha. The point is "same syntax means I don't have to remember anything else". Not pax, not sed, not bash a for loop. Commented Apr 8, 2018 at 13:37
0

OK, so I managed to get away with plain old sed like this.

ls -1 \
 storage/{innobase,xtradb}/pars/{pars0grm.cc,pars0grm.y,pars0lex.l,lexyy.cc} \
 storage/{innobase,xtradb}/fts/{fts0blex.cc,fts0blex.l,fts0pars.cc,fts0pars.y,fts0tlex.cc,fts0tlex.l} \
 storage/innobase/include/fts0[bt]lex.h \
 | sed -re 'h; s|/pars/|/|; s|/fts/|/|; s|innobase/include|include|; H; x; s|\n| |' \
 | xargs -L1 cp -v \
 ;

The sed script is not so trivial, so let me explain step-by-step (for future me):

  • h copies the current line ("pattern space") into a register ("hold space");
  • each s edits the pattern space as usual, hold space doesn't change;
  • H appends the now edited line to the hold register — which ends up holding two lines, the old filename and the new one, with embedded \n characters of course;
  • x exchanges the hold space with pattern space, effectively loading the old-new pair from the hold register;
  • s|\n| | flattens the two lines into one, separating them with space;
  • and finally, an implicit p (from calling sed without -n) prints out the result.

Altogether, the pipeline works as follows:

  • ls -1 expands all the globs into filenames of real existing files, and prints them one-per-line;
  • sed turns that into "$old_filename $new_filename", again one-file-per-line;
  • xargs -L1 calls cp -v $old_filename $new_filename on every line.

That's it!

answered Jun 16, 2016 at 14:14

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.