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
3 Answers 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?
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
andOldFile.h
are renamed toNewFile.c
andNewFile.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
-
So... while trying to avoid more new tools, you basically produced a new tool? :)ulidtko– ulidtko2018年04月08日 08:28:26 +00:00Commented 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.Jeff Ward– Jeff Ward2018年04月08日 13:37:54 +00:00Commented Apr 8, 2018 at 13:37
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 callingsed
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
callscp -v $old_filename $new_filename
on every line.
That's it!