5

Assuming I want to change some filenames that end with jpg.jpg to end only with .jpg (in bash), and I want to do it by piping the output of find to xargs:

By using sed:

find . -iname '*jpg.jpg' | xargs -I % mv -iv % $(echo % | sed 's/jpg.jpg/.jpg/g')

However, this does not replace jpg.jpg with .jpg in the destination file of mv.

By using awk:

find . -iname '*jpg.jpg' | xargs -I % mv -iv % $(echo % | awk '{gsub(/jpg.jpg/,".jpg")}; 1')

This neither does any replacement. Have I missed something?

Thor
  • 45,082
  • 11
  • 119
  • 130
nrz
  • 10,435
  • 4
  • 39
  • 71

5 Answers5

5

I would do this by writing a script that takes filenames and does the rename:

#!/bin/sh

for FILE
do
    mv $FILE `basename $FILE jpg.jpg`.jpg
done

That script is easily called via xargs.

If you want to smash it onto a single command line you can do it but it's usually not worth the trouble.

EDIT: If I had to do it on one command line I'd probably use a different trick without xargs: Use sed to turn the output of find into a shell script:

find . -iname '*jpg.jpg' | sed -e 's/\(.*\)jpg\.jpg$/mv & \1.jpg/' | sh

The advantage of xargs being able to fork off fewer children is defeated by the need to run mv repeatedly anyway. If you really need that advantage you need a solution like my first option but coded in something like perl which can execute multiple rename() calls without forking off any mv.

Ben Jackson
  • 90,079
  • 9
  • 98
  • 150
  • @EdMorton: Generally true, but I felt it would be confusing to dwell on those details when `xargs` is going to split filenames at spaces anyway. If you take Ed's advice, use `... -print0 | xargs -0 ...` as well. – Ben Jackson Nov 01 '12 at 04:27
4

If you have the rename command available on your system, then the following command should work:

rename 's/(.*)jpg.jpg/$1.jpg/g' *jpg.jpg
Muhammad Soliman
  • 21,644
  • 6
  • 109
  • 75
Sicco
  • 6,167
  • 5
  • 45
  • 61
3

The $(...) is evaluated by bash before running xargs, so xargs just sees the output of that expression as it currently stands as the second argument to mv.

So it is equivalent to:

... | xargs -I % mv % %
huon
  • 94,605
  • 21
  • 231
  • 225
  • 1
    What is the correct way to get around this (run sed on the original string, yet have the whole sed operation embedded in the middle of a command that uses the original, unsedded string? See my related issue: http://stackoverflow.com/questions/21636184/using-sed-on-xarg-variable-isnt-working-inside-shell-expansion – Excalibur Feb 07 '14 at 19:35
2

I would go with GNU parallel for this:

find . -iname '*jpg.jpg' | parallel mv {} {.}

{} is replaced by the input, {.} is replaced by the input minus last extension. Use --dry-run with parallel to see what will be done.

Edit - should handle file1jpg.jpg

Depending on your needs with regards to keeping case, this can be done with parameter expansion or with GNU sed:

Parameter expansion:

find . -iname '*jpg.jpg' | parallel v={}\; mv '{}' '${v/jpg.jpg/.jpg}'    

With GNU sed:

find . -iname '*jpg.jpg' | parallel mv '{}' '$(echo {} | sed -r "s/jpg\\.(jpg)$/.\1/I")'
Community
  • 1
  • 1
Thor
  • 45,082
  • 11
  • 119
  • 130
  • If I have eg. a file `file1jpg.jpg`, `find . -iname '*jpg.jpg' | parallel mv {} {.}` renames `file1jpg.jpg` to `file1jpg`. What I want is to rename `file1jpg.jpg` to `file1.jpg`, not `file1jpg`. – nrz Nov 01 '12 at 00:34
  • @nrz: It can still be done with parallel, just becomes more complicated. See edit. – Thor Nov 01 '12 at 09:58
  • The command using parameter expansion works, but the command using GNU `sed` fails with: `mv: missing destination file operand after `./file2jpg.jpg'` `Try `mv --help' for more information.` – nrz Nov 01 '12 at 19:27
  • @nrz: what commands is parallel trying to run? you can list them with `--dry-run`. – Thor Nov 01 '12 at 22:09
  • If there is a file `file1jpg.jpg`, `find . -iname '*jpg.jpg' | parallel --dry-run mv '{}' '$(<<< {} | sed -r "s/jpg\\.(jpg)$/.\1/I")'` prints the following: `mv ./file1jpg.jpg $(<<< ./file1jpg.jpg | sed -r "s/jpg\\.(jpg)$/.\1/I")` . – nrz Nov 01 '12 at 23:43
  • @nrz: I was testing it with zsh where the `<<<` wasn't causing any problems, with it seems `echo` is needed. Try it now. – Thor Nov 02 '12 at 09:29
  • In bash both `find . -iname '*jpg.jpg' | parallel v={}\; mv '{}' '${v/jpg.jpg/.jpg}'` and `find . -iname '*jpg.jpg' | parallel mv '{}' '$(echo {} | sed -r "s/jpg\\.(jpg)$/.\1/I")'` work as expected with `file1jpg.jpg`. – nrz Nov 20 '12 at 11:44
1

There's no need for xargs or find:

for i in *jpg.jpg; do mv "$i" "$(echo $i | sed 's/jpg.jpg$/.jpg/')"; done

And there's no real need for sed or awk either:

for i in $(find . -type f -name "*jpg.jpg"); do mv "$i" "${i/%jpg.jpg/.jpg}"; done

Here's another way:

find . -type f -name "*jpg.jpg" -exec bash -c 'mv $0 ${0/%jpg.jpg/.jpg}' {} \;
Steve
  • 51,466
  • 13
  • 89
  • 103
  • The first line works properly, the second gives error `mv: cannot move \`./myfilejpg.jpg' to \`./myfile.jpg/': Not a directory.` This can be fixed by changing `"${i/%jpg.jpg/.jpg/}"` to `"${i/%jpg.jpg/.jpg}"`. The third line seems to work with a similar fix. – nrz Oct 31 '12 at 22:19