1

I just stumbled upon Perl today while playing around with Bash scripting. When I tried to remove blank spaces in multiple file names, I found this post, which helped me a lot.

After a lot of struggling, I finally understand the rename and substitution commands and their syntax. I wanted to try to replace all "_(x)" at the end of file names with "x", due to duplicate files. But when I try to do it myself, it just does not seem to work. I have three questions with the following code:

  • Why is nothing executed when I run it?
  • I used redirection to show me the success note as an error, so I know what happened. What did I do wrong about that?
  • After a lot of research, I still do not entirely understand file descriptors and redirection in Bash as well as the syntax for the substitute function in Perl. Can somebody give give me a link for a good tutorial?
find -name "*_(*)." -type f | \
  rename 's/)././g' && \
find -name "*_(*." -type f | \
  rename 's/_(//g'  2>&1
Community
  • 1
  • 1
nst1nctz
  • 333
  • 3
  • 23

1 Answers1

2

You either need to use xargs or you need to use find's ability to execute commands:

find -name "*_(*)." -type f | xargs rename 's/)././g'
find -name "*_(*."  -type f | xargs rename 's/_(//g'

Or:

find -name "*_(*)." -type f -exec rename 's/)././g' {} +
find -name "*_(*."  -type f -exec rename 's/_(//g'  {} +

In both cases, the file names are added to the command line of rename. As it was, rename would have to read its standard input to discover the file names — and it doesn't.

Does the first find find the files you want? Is the dot at the end of the pattern needed? Do the regexes do what you expect? OK, let's debug some of those too.

You could do it all in one command with a more complex regex:

find . -name "*_(*)" -type f -exec rename 's/_\((\d+)\)$/$1/' {} +

The find pattern is corrected to lose the requirement of a trailing .. If the _(x) is inserted before the extension, then you'd need "*_(*).*" as the pattern for find (and you'll need to revise the Perl regexes).

The Perl substitute needs dissection:

  • The \( matches an open parenthesis.
  • The ( starts a capture group.
  • The \d+ looks for 'one or more digits'.
  • The ) stops the capture group. It is the first and only, so it is given the number 1.
  • The \) matches a close parenthesis.
  • The $ matches the end of the file name.
  • The $1 in the replacement puts the value of capture group 1 into the replacement text.

In your code, the 2>&1 sent the error messages from the second rename command to standard output instead of standard error. That really doesn't help much here.

You need two separate tutorials; you are not going to find one tutorial that covers I/O redirection in Bash and regular expressions in Perl.

The 'official' Perl regular expression tutorial is:

  • perlretut, also available as perldoc perlretut on your machine.

The Bash manual covers I/O redirection, but it is somewhat terse:

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks, that really halped me a lot. But I still do not understand why there is no xargs or exec used in the [example I mentioned](http://stackoverflow.com/questions/2709458/bash-script-to-replace-spaces-in-file-names). There, the rename command followed by the regex is sufficient to remove blanks in the file name. Also, does it matter if I work with exec or xargs? Is one of the solutions more appropriate for this purpose? – nst1nctz Nov 23 '13 at 20:27
  • It depends on the definition of the `rename` or `prename` command. Some versions may read file names one per line; the version I have (originally picked up from the 1st Edition of the Camel Book) does that. If invoked with command line arguments, it renames those; if invoked with no command line arguments (other than the regex), it reads standard input, splitting on newlines, and processes those as files. My version is careless enough to slurp the whole of standard input; this is sub-optimal if dealing with gigabytes of file names. – Jonathan Leffler Nov 23 '13 at 20:31