Need a little help with my first shell script. Basic image resize script...

Hey everyone, just now joined because I didn't want to go onto Ubuntu forums and start asking about how to write shell scripts. Seems like this is a pretty active forum for exactly what I need.

I'm trying to modify a shell script I found online, the end goal is to have it find all files in the directory it's ran AND all subdirectories, named folder.jpg, Folder.jpg, maybe a couple more, and make thumbnails of them.

I found this script earlier, which initially was trying to resize ALL files.. mp3 and what not (because of the FILES=* I'm guessing):

#!/bin/bash
FILES=*
for f in $FILES
do
echo �Processing $f file...�

convert -thumbnail 235x $f sm_$f

done

So, I looked at this and figured I could probably replace the FILES=* with FILES=folder.jpg. Sure enough, it worked. But when I then assumed that FILES=folder.jpg Folder.jpg would work for both, I was sadly disappointed. No luck with a comma and space, comma no space, semicolon, etc. I believe with a semicolon after the first file, the script at least ran again.... lol

I've been trying to look it up but having no luck, there has to be some easy way to specify multiple file names in that situation.

As far as making it work on all subdirectories as well, I couldn't find much on that, but I did manage to read that I could do this....

find /your/dir/with/subdirs -type d -exec sh -c 'cd "{}" ; /path/to/your/script.sh ;' \;

Sure enough, it found the file I put in my test directory named folder.jpg, as well as the one in a nested directory! Would be nice to have it all packaged in a script just for the sake of not having to copy and paste that every time, but that works for now...

Also I just read about the option of making a "loop wrapper", like this...

#! /bin/bash
cd /path/to/top/level/directory

for d in */ ; do 
  pushd $d
  # call your script here
  popd
done

And plan on trying that here in a little. SO, it sounds like I'm good to go on the subdirectories part. But what about the multiple file names? Is there a way I can just put it in to the script easily, or I imagine somehow I could parse it into that find command I was using.

Also, with the original script it creates the thumbnails with the name "sm_*original-filename*.... If possible I'd like to change that to have them be named something like folder_small.jpg, or even something completely different like album_art_thumb.jpg.

When I tried changing "convert -thumbnail 235x $f sm_$f" to "convert -thumbnail 235x $f $f_small", it was a no-go. Didn't want to run at all. I imagine there's an easy way to fix that but this language is all new to me. I don't know anything besides some HTML, CSS, and how to get around on a Unix/Linux shell (been using nothing but Linux as my OS for about 8 years or so now... off and on before that since I was a little kid).

If you could please take the time and point me in the right direction, I'd really appreciate it! Again just trying to:

  1. Specify multiple filenames to target, in the FILES= or otherwise...
    and 2. How to change the end filename from sm_* to something that isn't text followed by the old filename.

And if you've got the time, maybe even help with the part about making it work recursively on all sub-directories.

Thanks! Glad to have found this forum. I can see how learning to make these scripts could really make a lot of things easier in the future... :slight_smile:

Zac

Perhaps, something like this

find . -type f -name "*.jpeg" | while read f
do
    echo "Processing ${f}�"
    convert -thumbnail 235x "$f" sm_"$f"
done

Based on your snippets; not tested.

Hmm.. well that one didn't do anything for some reason. Not sure if it's because of me changing it to folder.jpg instead of the *.jpeg you had... I have one directory with a very large library of music and subdirectories, with additional album art that doesn't need to be converted. So just looking to do folder.jpg and maybe a couple more at the same time if possible. If not, I can always change the script and run for the other files.

I did manage to get a script running that will work recursively for one file. However, it only works when it's set up to name the new file something like "$f-thumb.jpg"... meaning it comes out like folder.jpg-thumb.jpg. When I just replaced the whole $f part with cover_small.jpg or something, it only worked on the main directory, and not subs.

Any ideas how I could fix that? Other than that, looks like I am getting there! This is actually quite a bit of fun.

Yes, changing the -name "*.jpeg" to -name "folder.jpg" will only find files named as such and if it does not find any, well, it does nothing.

Here's an explanation of the find command

find . -type f -name "*.jpeg" # look for only files (no directories) and the file has to be be "anything.jpeg" recursively in each directory, starting at current directory

Would changing to just -name "*.jpg" work?

Sorry... I was trying to be as clear as possible. Maybe this is just a strange request, and that's why I can't find a script for it already.

I am not looking to convert all my .jpg files, only the ones named folder.jpg (the standard I decided on, for album cover art that I want to appear). If possible it'd be great to have it work for Folder.jpg and .jpeg of each too, since I'm sure I missed some.... but if I need to I can just edit the script and run a few times.

I was able to finally get it to work in the main dir and one below it, but I don't think it will go any further than that. The way that my filesystem is set up, I'm really going to need it to, or this will still take a good deal of manual labor.

Here is the script I am running now...

#!/bin/bash
for f in `find . -name "folder.jpg"`
do
    convert $f -resize 200x200\!  $f-thumb.jpg
done

The way it's set up now, the thumbnail files end up with names like folder.jpg-thumb.jpg. Is there any way I can change that to either adding something before the filename (i.e. small_folder.jpg) or an entirely new filename (i.e album_art_small.jpg)?

Tried just putting the filename I want instead of the $f-.jpg, and then it would only work in the main directory for some reason.

And again... pretty sure this script as I have it now will only work one subdirectory down. That's something I'll need to fix one way or another.

Thanks for your help! It is much appreciated.

Zac

From your last example, try substitution:

convert $f -resize 200x200\!  ${f/\.jpg}-thumb.jpg

hth

I suppose you are generally interested in learning to write shell scripts and not only to make the script going you are attempting to write. Therefore a little explanation why this (any other constructs similar to this) is a VERY BAD IDEA and you should never do it:

The "*" is interpreted by the shell first and replaced with its result: a list of all entries in the current directory. It is never a good idea to work with relative pathes inside a script. Execute a scriptin another path and this might mix up everything and render your system unusable.

Aside from this: say the directory contains 3 entries, "fileA", "fileB" and "fileC", then the line would be equivalent to:

FILES=fileA fileB fileC

The first problem is: not all entries in a directory have to be files. There are directories, links, FIFOs and more. Chances are your "convert" utility does not know how to handle them at all because it is built to work with a certain type of files and nothing else.

Another problem is: suppose you have lots of such files. A UNIX directory can easily handle millions of files (i have a few such systems). Imagine what your for-loop will do when it is presented this line:

for f in fileA fileB fileC ...many millions more... lastfile

The shell will simply give up and issue an "too many arguments"-error.

This is why such for-loops are never correct and always to be avoided. If you want to cycle through a list of files do it this way:

ls /path/to/dir | while read FILE ; do
     echo working on $FILE
done

If you want to modify your list you can either use shell-globs (*,?, ...) or text-filters to alter the list. For example, filter out every file with a number in the filename:

ls /path/to/dir | grep -v "[0-9]" | while read FILE ; do
     echo working on $FILE
done

Now, back to your problem, for which the find -command is the correct wa to do it.

I have written a little introduction about how find works.

Yes, because $f_small would be the content of a (non-existent) variable named "f_small". You can do whyt you want to do, but you need "variable expansion" for this, which is a bit complicated and probably not the first thing you want to learn.

For the sake of completeness, here you are:

f="example.jpg"

f_name="${f%.*}"
f_ext="${f##*.}"

echo "filename: $f_name   extension: $f_ext"

echo convert -thumbnail 235x "$f" "$f_name"_thumb."${f_ext}"

There are two ways to achieve this: put in multiple "-name"-clauses into the find-statement:

find /your/start/dir -type f \( -name "folder.jpg" -o -name "FOLDER.JPG" -o -name ....\) -exec ....

A second way is, when the names are related enough, to construct a single name-clause with a wildcard:

find /your/start/dir -type f -name "[Ff]older.jpg" -exec ....

Would find all files named "Folder.jpg" or "folder.jpg", for instance.

I hope this helps.

bakunin

A few things to be aware:
Using a for loop here limits to files that do not have spaces, if any file matched by find contains spaces the script would introduce a behavior you do not want. And by the way, find will process any directory tree recursively.

find . -name "folder.jpg" will not be limited to only files but to directories that contains that name as well.

Here's another untested version that might process any files named folder.jpeg or folder.jpg in any directory starting in the current one.
If you like what echo produces, comment it and uncomment the convert.
As an example it does change the name with one of your new names suggestions.

#!/bin/bash

find . -type f -name "folder.jp*g" | while read f
do
  path="${f%/*}"
  extension="${f##*.}"
  echo "$f -> ${path}/album_art_small.${extension}"
  # convert "$f" -resize 200x200\!  "${path}"/album_art_small."${extension}"
done

Thank you! That solved one problem. I tested it, and it will actually work a couple directories down, too. Only it won't if there are any spaces in the directory name. Is there any way to fix this?

If the above script is working for you except for the set of filenames to be processed and the output names your want, let's try the following:

#!/bin/bash
find . -name '[Ff]older.jpg' -o -name '[Ff]older.jpeg' |
while read -r f
do
	convert -resize 200x200\! "$f" "${f%/*}/thumb-${f##*/}"
done

The find command in this script will identify files named Folder.jpg , folder.jpg , Folder.jpeg , and folder.jpeg anywhere in the file hierarchy rooted in the current directory. If you want to add more filenames to the list of those to be processed, add an additional -o 'pattern' for each additional filename matching pattern desired to the end of the find command. For each of the files found, this script it will create a file in the directory where that file was found with the prefix thumb- added to the final component of the pathname.

Using find ... | while read ... f instead of for f in `find ...` allows the find command to run in parallel with the thumbnail creations (instead of creating a complete list of files to process before starting to create the thumbnails) and requires less memory in the shell to get the job done. The quotes added to the $f parameter expansions protect against the possibility of your directory names containing any spaces, tabs, or pathname expansion characters. The ${f%/*} parameter expansion extracts the pathname of the directory in which the file resides and the ${f##*/} parameter expansion extract the final component of the pathname. Using those two expansions allows us to insert the thumb- filename prefix in the correct spot in the output file pathname. If you just want to use the filename album_art_small.jpg as your output filename, the convert command in the above script can be changed to:

	convert -resize 200x200\! "$f" "${f%/*}/album_art_small.jpg"

but note that if you do that and you happen to have two or more files to be processed in a single directory, the last one processed will be the only thumbnail that is kept (thumbnails processed earlier will be overwritten by thumbnails processed later).