41

So, I have the following structure:

.
..
a.png
b.png 
c.png

I ran a command to resize them

ls | xargs -I xx convert xx -resize xx.jpg

Now my dir looks like this

.
..
a.png.jpg
a.png
b.png.jpg
b.png
c.png.jpg
c.png

The firs question is, how do i rename the file so that I can just have one extension. Not two. (basically, how do I clean up my original mistake)?

The second question is, in the future, using xargs, how do I change the extension of the file simular to second command?

Tim Pote
  • 27,191
  • 6
  • 63
  • 65
Cripto
  • 3,581
  • 7
  • 41
  • 65
  • Move into the folder, then `rename '.png.jpg' '.jpg' ./*` (I usually make a copy of all the files and use `mogrify` instead of `convert`). – Matej Jun 10 '12 at 20:40
  • @MatejNanut Feel free to write that up into a full fledged answer. While it does depend on you having `rename` and `mogrify` available, it's certainly shorter than my solution. – Tim Pote Jun 10 '12 at 20:51
  • Thanks for the suggestion, I did so. Note that `mogrify` should be available on his machine, as it comes with `convert`. `rename` comes from a package on which `mkinitcpio` and `init` depend on my machine, so it's probably available on his too? – Matej Jun 10 '12 at 21:07
  • Possible duplicate of [ImageMagick: convert to keep same name for converted image](https://stackoverflow.com/questions/27778870/imagemagick-convert-to-keep-same-name-for-converted-image) + https://stackoverflow.com/questions/16541582/finding-multiple-files-recursively-and-renaming-in-linux – Ciro Santilli OurBigBook.com Jan 12 '19 at 20:49

10 Answers10

84

This can be also be done with xargs and sed to change the file extension.

ls | grep \.png$ | sed 'p;s/\.png/\.jpg/' | xargs -n2 mv

You can print the original filename along with what you want the filename to be. Then have xargs use those two arguments in the move command. For the one-liner, I also added a grep to filter out anything not a *.png file.

Schleis
  • 41,516
  • 7
  • 68
  • 87
  • 3
    Beautiful! Didn't know about `sed 'p'` nor about `xargs -n2`. – rturrado Sep 10 '15 at 12:58
  • 11
    Does this work if the paths contain spaces? I don't think so. `find . -name "*.png" -print0 | sed 'p;s/\.png/\.jpg/' | xargs -0 -n2 mv` is better I think. – adrianN May 26 '16 at 14:37
  • By far the best answer here. – meh Feb 04 '19 at 20:12
  • 2
    I'd start with `... | xargs -0 -n2 echo mv` first, and proceed with `... | xargs -0 -n2 mv` if result is correct. – Alexander Vasiljev Sep 02 '20 at 10:41
  • any reason to use `mv` instead of `rename`? – CervEd Apr 25 '21 at 19:25
  • @adrianN Does `find ... -print0` work with GNU sed? Even though this question is tagged #linux, this version works for me with BSD sed on macOS: `find . -name "*.png" | sed -e 'p;s/\.png/\.jpg/' | tr '\n' '\0' | xargs -0 -n2 mv` – Ferd Mar 02 '22 at 16:37
  • 2
    @CervEd Not all systems have `rename`, and not all systems have the same `rename` – damd Jul 03 '22 at 18:29
25

how do i rename the file so that I can just have one extension.

cd dir/with/messedup/files

for file in *.png.jpg; do
  mv "$file" "${file%.png.jpg}.jpg"
done

in the future, using xargs, how do I change the extension of the file simular to second command?

To my knowledge, that can't be done. The best way to do it would be to use a for-loop with parameter substitution much like the one above:

for file in *.png; do
  convert "$file" -resize "${file%.png}.jpg"
done

If you have files in subdirectories that you want converted, then you can pipe find to a while read loop:

find . -type f -name '*.png' |
while read file; do
  convert "$file" -resize "${file%.png}.jpg"
done

NOTE: It's generally considered a bad idea to use the output of ls in a shell script. While your example might have worked fine, there are lot's of examples where it doesn't. For instance, if your filenames happened to have newlines in them (which unix allows), ls probably won't escape those for you. (That actually depends on your implementation, which is another reason not to use ls in scripts; it's behavior varies greatly from one box to the next.) You'll get more consistent results if you either use find in a while-read loop or file globbing (e.g. *.png) in a for loop.

Tim Pote
  • 27,191
  • 6
  • 63
  • 65
19

Coming late to the party, but here's how you can rename files with xargs. Say you have a bunch of files named fileN.svg.png and you want to name them fileN.png where N could be a series of integers:

ls *.svg.png | xargs basename -s .svg.png | xargs -I {} mv {}.svg.png {}.png

The first xargs uses basename to strip off both .svg and .png to get a just filenameN. The second xargs receives that bare name and uses replacement to rename the file.

Dethe
  • 209
  • 2
  • 6
  • this only works if the command is run under the folder which these files are located... .my attempt to use this, the mv command complains as it will not be able to 'stat' the file, – soMuchToLearnAndShare Jun 17 '20 at 09:42
8

To clean up your error, try the rename utility. Check the manpage for details.

In your case, you'd do rename '.png.jpg' '.jpg' ./* if your current directory is set appropriately.

Since you have convert available, I'm assuming you have mogrify too (imagemagick suite). Whenever I want to do this, I copy the files into a different directory and use mogrify instead. I usually need this only for resizing, but if you change the image format aswell, mogrify will handle the filenames (make new files with proper filenames).

You would use it as mogrify -format jpg -resize [size] ./*.png. I'm not sure what -resize without geometry arguments is supposed to do. It isn't documented and doesn't work on my machine.

As Tim Pote reasoned, I don't think you can make xargs handle filenames and extensions separately.

Matej
  • 625
  • 7
  • 14
6

I'm late to this party by about 3 years, I just had a similar problem which I figured out myself. I had a list of png files which I converted using inkscape, because ImageMagick's svg support is poor.

I originally converted them by doing:

find . -name "*.svg" -exec inkscape {} --export-png={}.png

Which of course led to the same issue like posted above.

file1.svg
file1.svg.png
file2.svg
file2.svg.png
file3.svg
file3.svg.png
file4.svg
file4.svg.png

I wanted to rename *.svg.png to *.png, this is what I wound up with...

find . -name "*.svg.png" -print0 | sed 's/.svg.png//g' | xargs -0 -I namePrefix mv namePrefix.svg.png namePrefix.png

This does three things:

  1. find in this directory files named *.svg.png, the -print0 prints to standard output
  2. sed modifies the standard output, basically swap .svg.png with nothing, so I'd get: file1/file2/file3/file4
  3. xargs -0 to get the data from sed, -I references the filename w/o the extension, then mv original filename to new filename. The word namePrefix isn't anything special, just wanted to make it clear.

EDIT

I realize now this is the most convoluted way to do this. One can simply use the rename command.

rename 's/svg\.png/.png/' *
Paul J
  • 1,489
  • 1
  • 17
  • 19
  • 1
    `rename` definitely was the simplest way for me: `$ find . -name *_12_0@2x.png | rename 's/_12_0\@2x.png/_12_0\@3x.png/' --stdin` (in this case, I wanted to change the `2x` to `3x in the file name) – Alan Zeino Oct 04 '18 at 21:51
  • 1
    I recommend using "sed -z", if you use null-byte actions. Otherwise sed will interprete the stream as binary. Less performant and more error prone. – Phil Nov 20 '18 at 08:24
  • 1
    You are never late on the internet. This was super helpful, I used a modified version of your one-liner. Thanks – james-see Sep 04 '19 at 15:51
4

After some investigation on similar task here is my code:

find . -maxdepth 1 -name '*.png' -print0 | sed 's/.png//g' | xargs -0 -I% -n 1 -P 8 convert -quality 100 %.png %.jpg

Reasoning:

  • renaming will not compress the file (so use convert instead of mv)
  • ls \.png$ | xargs will not deal with spaces in the path/filename
  • find . will search in sub-folders, so use -maxdepth 1
  • convert doesn't use available CPUs so -P8 (or -P other)
  • sed without 'g' at the end will not substitute all files (only one)
  • sed 's/.png//g' will leave no extension (basename could also work but didn't after -print0)
  • parallel - potentially better solution but didn't work on my Ubuntu 18.04 bash 4.4
  • % is the smallest common symbol for substitution (compare to {} xx namePrefix)
  • -n2 parameter is good for xargs but didn't work with -print0 properly (n number of entries to take and pass after xargs)
  • -quality 100 default magic quality is 92 (which is fine), here 100% to avoid loosing anything
Andreas
  • 2,455
  • 10
  • 21
  • 24
Alexey K.
  • 101
  • 4
3

My attempt from: https://www.tecmint.com/linux-image-conversion-tools/

ls -1 *.png | xargs -n 1 bash -c 'convert "$0" "${0%.png}.jpg"'

Using parallel

parallel convert '{}' '{.}.jpg' ::: *.png
SergioAraujo
  • 11,069
  • 3
  • 50
  • 40
1

My solution is similar to many of the xarg solutions, and particularly similar to Schleis'.

The difference here is a full regex manipulation with match references, and sed commands that properly ignore files that don't match so you don't need to prefilter your listing.

This is also safe for files with spaces and shell meta.

Change \2 in the replacement to any desired extension.

ls |
sed -nE 's/Rick\.and\.Morty\.(S03E[0-9]{2})\..*(\.[a-z0-9]{3})/"&" "Rick and Morty \1\2"/;T;p' |
xargs -n 2 mv

Explanation

The -n arg tell's sed not to print anything by default, the T command says skip to the end of the script if the previous s command didn't do a replacement, the p command prints the pattern space (only hit if the s command matches).

The & in the replacement is a reference to the contents of the original filename match.

If we replace mv in the command with bash -c 'echo "run($#) $@"' bash then we can see the number of times mv would be called, and with parameter count and value:

$ ls |
  sed -nE 's/Rick\.and\.Morty\.(S03E[0-9]{2})\..*(\.[a-z0-9]{3})/"&" "Rick and Morty \1\2"/;T;p' |
  xargs -n 2 bash -c 'echo "run($#) $@"' bash
run(2) Rick.and.Morty.S03E02.720p.HDTV.x264-BATV.mkv Rick and Morty S03E02.mkv
run(2) Rick.and.Morty.S03E03.720p.HDTV.x264-BATV.mkv Rick and Morty S03E03.mkv
run(2) Rick.and.Morty.S03E04.720p.HDTV.x264-BATV.mkv Rick and Morty S03E04.mkv
run(2) Rick.and.Morty.S03E05.HDTV.x264-BATV[ettv].mkv Rick and Morty S03E05.mkv
run(2) Rick.and.Morty.S03E06.720p.HDTV.x264-BATV.mkv Rick and Morty S03E06.mkv
run(2) Rick.and.Morty.S03E06.HDTV.x264-BATV.mkv Rick and Morty S03E06.mkv
run(2) Rick.and.Morty.S03E07.720p.HDTV.x264-BATV[ettv].mkv Rick and Morty S03E07.mkv
run(2) Rick.and.Morty.S03E08.720p.HDTV.x264-BATV.mkv Rick and Morty S03E08.mkv
run(2) Rick.and.Morty.S03E09.720p.HDTV.x264-BATV.mkv Rick and Morty S03E09.mkv
run(2) Rick.and.Morty.S03E10.720p.HDTV.x264-BATV.mkv Rick and Morty S03E10.mkv
joshperry
  • 41,167
  • 16
  • 88
  • 103
0

First time I answer, I hope it won't be too bad.

I had some sort of same trouble, I managed to solve it combining Schleis answer and adrianN comment. Trouble was with some files with space char or other special ones allowed in ext4.
It should add the ability to work with any UTF8 file name. Here is it:

find . -type f -iname \*.png.jpg -print0 | sed -z 'p;s/\.png\.jpg/\.jpg/' | xargs -0 -n2 mv

I have read (cannot remember where) that in the past sed (or xarg?) had trouble dealing with null character. Seems it's no more the case.

I've added some explanation below for people like me who like to understand what they do and not just copy past some answer. ;-)

Commands parameters:

find . : find in the current directory
-type f : something which is a regular file (ex: not a device, directory, etc.)
-iname : search based on file name but the match is case insensitive. Just in case your picture software write "PNG" instead of "png"
-print0 : separate what is found by NULL character

sed -z : consider the null character as the only separator of different element of text to process; both for the text received and the text modified
p : write the line received first
s/.png.jpg/.jpg/ : substitute .png.jpg by .jpg and write it

xargs -0 : Input items are terminated by a null character instead of anything else
-n2 : send the two arguments received from sed to mv

What each command does:

find :

file1.png.jpgNULLfile2.png.jpgNULLfile3.png.jpgNULLfile4.png.jpg

sed

file1.png.jpgNULLfile1.jpgNULLfile2.png.jpgNULLfile2.jpgNULLfile3.png.jpgNULLfile3.jpgNULLfile4.png.jpgNULLfile4.jpg

xargs

mv file1.png file1.jpg  
mv file2.png file2.jpg  
mv file3.png file3.jpg  
mv file4.png file4.jpg  
-1

find ./xx -name "*.png" -print0 |sed 's/.png$//g'|xargs -0 -I% mv %.png %.jpg

I like to use following command

find ./xx -name "*.png" -type f|while read FILE; do
  mv "$FILE" "$(echo $FILE|sed -e 's/.png$/.jpg/')";
done
xsilen T
  • 1,515
  • 14
  • 10