Hi Piers,
On Thu, Jan 12, 2023, at 18:49, Piers Rowan via luv-main wrote:
...
> I fell like I've asked this before or something similar.
>
> I have a structure like:
>
> /Dir1/123.junk.doc
> /Dir1/456.junk.pdf
> /Dir1/SubDir/1123.junk.doc
> /Dir1/SubDir/1456.junk.pdf
> /Dir2/SubDir/4321.junk.doc
> /Dir2/SubDir/7676.junk.pdf
> ...etc...
>
> I want some guidance as to how to make:
>
> 1123.junk.doc > 1123.doc
>
> $ID.junk.$EXT > $ID.$EXT
>
> Your thoughts are appreciated.
...
I see you've already got a good-enough solution to this, but in
case you ever need to do something similar in future:
I'd do something like:
find /Dir1 -type f | perl -lne '$o=$_; s/\.junk\././; print("mv -i $o $_") if
$_ ne $o;'
That is:
1. The find gives you a list of all the file names under /Dir1
2. The -lne to perl makes it (-e) run the given expression (quoted in
single quotes to protect shell metacharacters) on (-n) every input
line (that is, every file name), stripping off trailing
newlines on input, and putting them back on on output (-l).
3. The perl code saves the current line $_ into a variable $o
(for "old" or "original"), then does a substitution on the
current line (implicitly $_). In the regular expression,
plain dot is a match-anything metacharacter, so it needs to
be backslash escaped to match a literal dot. So we have the
old and new versions of the file name in those respective
variables.
4. Instead of doing the renaming immediately, the perl code
outputs a line-by-line list of shell mv commands to do the
actual renaming (with -i for interaction if it'd try to
over-write an existing file). That way, you can inspect the
list of shell commands, and check that they're doing the
right thing before committing. Good for debugging.
5. Notice that the mv command is emitted only if the
substitution actually made a change, so files that don't
match won't be affected.
6. Once you're happy that the shell mv commands would do the
right thing, you can make it happen by piping the output of
that above command pipeline into sh, like:
find /Dir1 -type f | perl -lne '$o=$_; s/\.junk\././; print("mv -i $o $_")
if $_ ne $o;' | sh
Notes:
- Being too lazy to set up a test file structure, I haven't
actually tried out the above. But I've done similar things
many times. The main thing is that you can inspect the list
of commands output, so you can verify yourself that they look
right before running them. And even with that, it'd still be
a good idea to do this on a copy of your actual data (or have
a backup of it).
- This is a very general strategy: Instead of writing a script
to actually do something (which might go horribly wrong); you
write a script that emits a simple list of commands, which can
be visually inspected before being piped into the shell to be
executed.
- The perl 's' "substitute" operator in its plain form will
substitute only the first occurrence it sees. The above
assumes this will be only in the filename parts. It's a bit
more complicated if that string you want replaced can also
occur along the directory path.
- The find will list the files in whatever order it encounters
them in the filesystem. If you want them in some sane order,
you can insert the sort command into the pipeline.
- This is finding only ordinary files, it won't see symbolic
links. If you have symbolic links in your directory
hierarchy, then you'll need to decide what you want to do with
them, and put in additional options to find to achieve that.
But I don't this applies in your case.
- This will come unstuck if your filenames might contain spaces
or other whitespace. To handle this, you'll need to put
(say) double quotes around the filenames in the generated
commands, by putting suitable backslashed-escaped double
quotes into the output string, something like:
"mv -i \"$o\" \"$_\""
(The good Plan 9 people avoided this Unix shell-quoting hell in
their shell.)
- If your filenames might contain more exotic things, like
newlines or dollar signs, then you'll need to do stuff like
work with null-byte-terminated lines (by -print0 on find and
the corresponding options to perl, which I don't off the top
of my head remember). And concatenation contrivances to get
single quotes into the string printed by perl. But by then
you're better off writing a script (in perl or your favorite
scripting language), rather than trying to do it all in a
one-liner, with all its escaping weirdness.
I hope this is of some use.
— Smiles, Les.
_______________________________________________
luv-main mailing list -- [email protected]
To unsubscribe send an email to [email protected]