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]

Reply via email to