LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (https://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   Script to rename files (https://www.linuxquestions.org/questions/linux-newbie-8/script-to-rename-files-865089/)

MikeG001 02-25-2011 10:32 PM

Script to rename files
 
Please bear with me... I have some linux experience. But I'm stumped.

I have 500+ directories with a single file in each directory.
I would like to rename each file in the directory with the directory name and move the file to another directory.

I can do this manually, but there must be a script to to this.

System Details: Fedora 14

Any help?

jbiggs12 02-25-2011 11:51 PM

You should be able to set up a simple bash script by using the ls command while you're in the directory containing the folders, and then cd into each dir and copy the file back. Try looking at some bash tutorials. ls will list the files and directories in a specific directory, and mv will move and/or rename files. For example, if I wanted to see all of the files in my home directory, I would type:
Code:

ls /home/USER
, and to move (and rename) a file I would type
Code:

mv /home/USER/name/name /home/USER/name
.

Sorry if I'm not that much help to you, I don't program bash but it should be a fairly easy first project :)

Dark_Helmet 02-26-2011 12:20 AM

Perhaps I can help, but you need to flesh out the problem a little more.

How are the directories and files organized? Is there a single directory that all of them are under? For instance:
/home/usera/myfiles
/home/usera/myfiles/dir01/filename
/home/usera/myfiles/dir02/filename
/home/usera/myfiles/dir03/filename
...

Do the filenames follow any sort of pattern? Do they all have the same name--just differentiated by the parent directory?

When you say you want the directory added to the filename, which part of the directory? Just the parent directory? The full, absolute path? Since you can't include a '/' as part of a filename, what character would you like to use to separate the path component(s) from the filename?

In a nutshell, it would probably be easiest to give an example of what your set up looks like as it stands now, and then show what you would like it to be after the script runs.

MikeG001 02-26-2011 01:02 PM

jbigs12/Dark_Helmet thanx for the response,

The directory structure is as follows.

/home/usera/myfiles
/home/usera/myfiles/Kathy Johnson/image123.jpg
/home/usera/myfiles/Fred Smith/image324.jpg
/home/usera/myfiles/Bill Smith/image 657.jpg

I would like to have.

/home/usera/myfiles/new_Directory/Kathy_Johnson.jpg (For /home/usera/myfiles/Kathy Johnson/image123.jpg)
/home/usera/myfiles/new_Directory/Fred_Smith.jpg (For /home/usera/myfiles/Fred_Smith/image324.jpg)

The files are coming from a windows system hence the spaces. The only pattern is ..../Name/filename.jpg
I have the ability to relocate the complete structure to a single drive(s) if that makes the script easier.

The assistance is appreciated.

Dark_Helmet 02-26-2011 02:15 PM

Here is a possible solution based on that info.

Code:

#!/bin/bash

SOURCE_DIR=/home/usera/myfiles
DEST_DIR=/home/usera/myfiles/new_Directory

find ${SOURCE_DIR} -type f -iname "*.jpg" -print0 | while read -d $'\0' filename ; do
  removeSpaces=$( echo "${filename}" | sed 's@ @_@g' )
  parentDirectory=${removeSpaces%/*}
  newFilename="${DEST_DIR}/${parentDirectory##*/}.jpg"

  echo mv "\"${filename}\"" "\"${newFilename}\""
#  mv "${filename}" "${newFilename}"
done

Please note: this script will do nothing until the hash mark is removed from the mv command. Do not remove it until you are certain that the script will do what you expect. Even then, it's probably a good idea to run against a test data set. Or you might consider changing the mv to a cp--so that there is no risk of data loss.

You need to:
(1) modify the values assigned to SOURCE_DIR and DEST_DIR to match your setup
(2) make sure that DEST_DIR actually exists (i.e. use mkdir to create it if necessary)
(3) make at least one dry-run and make sure the echo statements are "sane"

This script will search for any file with a "jpg" extension beneath SOURCE_DIR. For any file found, the script will determine the new filename by (1) substituting underscores for spaces; (2) determining the immediate parent directory; and (3) adding a "jpg" extension to the parent directory name.

Things that would cause the train to go off the tracks: multiple jpg files in the same directory or multiple subdirectories that share the same name (e.g. SOURCE_DIR/Kathy and SOURCE_DIR/somesub/Kathy).

In the case of multiple files in one directory, subsequent jpg files would overwrite previous jpg files (because all files would share a common parent directory, and hence a single destination filename).

Similarly, if multiple directories share the same name and each has a jpg file, the script would generate the same filename for each file and subsequent files would overwrite previous ones.

Lastly, the script will not remove the directories that are emptied after the script runs. You'll need to do that manually if there is no other purpose for them.

mmhs 02-26-2011 02:18 PM

copy this script to /home/usera/myfiles and run it ;) (
Code:

#!/bin/bash
mkdir ./new_Directory
for file in $(find . -name *.jpg)
do
m=$(basename $file)
f=$(echo $file|awk -F/ '{print $2}')
echo $f
echo $m
mv ./$f/$m ./$f/$f.jpg
cp ./$f/$f.jpg ./$f/new_Directory
done


Dark_Helmet 02-26-2011 02:35 PM

Be cautious: mmhs's script does not handle filenames with spaces in them. Given that the subdrectories and some of the filenames themselves have spaces, that is a serious concern.

kurumi 02-26-2011 06:52 PM

Ruby(1.9+)

Code:

#!/usr/bin/env ruby 
newdir="/home/usera/myfiles"
Dir.mkdir(newdir) if not Dir.exists?(newdir)
Dir["/home/usera/myfiles/**/im*jpg"].each do |file|
  filename = File.dirname(file).split("/")[-1].gsub(/\s+/,"_")+".jpg"
  directory = [newdir,filename]
  File.rename( file , directory.join("/") )
end


MikeG001 02-26-2011 09:06 PM

Dark_Helmet, thanx.

Let me understand (my learning) what you have done.
============
#!/bin/bash

SOURCE_DIR=/home/usera/myfiles
DEST_DIR=/home/usera/myfiles/new_Directory

find ${SOURCE_DIR} -type f -iname "*.jpg" -print0 | while read -d $'\0' filename ; do
removeSpaces=$( echo "${filename}" | sed 's@ @_@g' )
parentDirectory=${removeSpaces%/*}
newFilename="${DEST_DIR}/${parentDirectory##*/}.jpg"

echo mv "\"${filename}\"" "\"${newFilename}\""
# mv "${filename}" "${newFilename}"
done
===========
1) Find all files (-type f) with pattern *.jpg (-iname "*.jpg") (Used " to ensure you have full file name including spaces?) then print to Stdout
2) Piped that to a while loop (while ; do ..... done)
3) In the while loop read until no file name(read -d $'\0') ('\0' Null character?)
4) Take filename and put into variable filename
5) Convert spaces to _ using sed and put into variable removeSpaces
- why the echo?
6) create variable parentDirectory
- lost me here, does the %/* just capture the directory name.
7) create variable newFilename
- Think I understand this line
7) Then why the echo? Just to see output? == echo mv "\"${filename}\"" "\"${newFilename}\"" ==
8) Actual move command


Do have this right?

There is no way I could have done this. Thank you very much.
Sorry to ask again, but I like to learn

mike g.

Dark_Helmet 02-26-2011 10:38 PM

Quote:

1) Find all files (-type f) with pattern *.jpg (-iname "*.jpg") (Used " to ensure you have full file name including spaces?) then print to Stdout
2) Piped that to a while loop (while ; do ..... done)
3) In the while loop read until no file name(read -d $'\0') ('\0' Null character?)
4) Take filename and put into variable filename
Mostly, yes. Just for reference while I explain what I did:
Code:

find ${SOURCE_DIR} -type f -iname "*.jpg" -print0 | while read -d $'\0' filename ; do
...
done

'-type f' -- only match actual files
'-iname "*.jpg"' -- case insensitive match. Find any name that ends with .jpg (e.g. .jpg, .JPG, or any case-combination)

The double quotes around the *.jpg is simply habit. It's born out of frustration dealing with unanticipated/unexpected wildcard expansion. The double quotes just make sure the shell keeps its grubby hands off. The space-handling is done through other means.

'-print0' -- part of the space-handling in the filenames. This tells find to put a NULL character after each matching filename rather than simply a newline. The NULL is used as a marker for the read command.

The results are sent to stdout, and stdout is piped into the read command.

"-d $'\0'" -- causes the read to store everything up to a NULL character into the filename variable. Spaces, tabs, pretty much anything. Whereas without the --print0 and -d options to find and read respectively, the shell loop would separate the find output at any whitespace (space, tabs, newlines, etc.) causing the filename variable to have a piece of the actual path/file.

There are other ways of handling spaces (e.g. modify the IFS shell variable). So if my approach just feels too cryptic, you can probably find another approach that sits better with you.

Quote:

5) Convert spaces to _ using sed and put into variable removeSpaces
- why the echo?
The echo is necessary to give the filename to sed for processing. Sed will process information from a file or from standard input. Since the information that needs to be altered is in a variable, the echo is a pretty simple way of getting the variable's contents to standard output and then piped to sed.

You could do the same thing with parameter expansion (which I'm about to get to), and it would use the same basic syntax as sed. So, I'm guilty of falling into habit (again) for this step.

Quote:

6) create variable parentDirectory
- lost me here, does the %/* just capture the directory name.
Code:

parentDirectory=${removeSpaces%/*}
This is a special form of parameter expansion. So, this tells bash to take the contents of the variable removeSpaces and remove the shortest part of the variable's contents that match the pattern after the '%'. Using the '%' makes the pattern matching start looking from the end of the variable's contents (as opposed to the beginning).

The asterisk is a wildcard that matches anything, and the '/' is a literal slash. removeSpaces contains the full path to the file including the filename. If you remove everything from that full path after (and including) the last slash, you'll be left with the full path to the parent directory--the filename and the last slash will have been stripped.

Quote:

7) create variable newFilename
- Think I understand this line
Code:

newFilename="${DEST_DIR}/${parentDirectory##*/}.jpg"
Ok, another special form of parameter expansion here. Let me tackle that first. Using the double-hash mark (#) means to remove the longest part of the variable's contents that match the pattern after the '##'. Also, using the hash form makes the pattern matching start from the beginning of the variable's contents.

Again, the asterisk is a wildcard that matches anything, and the '/' is a literal slash. So everything in the variable up to and including the last slash is stripped off--which leaves the immediate parent directory name.

The rest of the assignment adds the destination directory to the front and the ".jpg" extension to the end.

You can read more about the special forms of parameter expansion by opening man bash and searching for the parameter expansion section header (e.g. when inside the man page, type '/Parameter Expansion', press Enter, and hit 'n' (if necessary) until you reach the start of the section). You'll see the space-to-underscore style I alluded to before: ${parameter/pattern/string} as well as the discussion of the other forms I used.

Quote:

7) Then why the echo? Just to see output? == echo mv "\"${filename}\"" "\"${newFilename}\"" ==
This was to satisfy you that things are working. This is basically showing what the mv command would look like if it were uncommented.

As a rule of thumb, you should never trust your data to a script or program from the internet until you're sure it will work. There may be no malice involved, but a bug or an unanticipated environment quirk could cause data loss. Getting that data back could be easy (backups) or impossible depending on your situation.

Quote:

8) Actual move command
Yup. And again, remove the '#' when you're comfortable it won't screw anything up.

EDIT: And one last thing--the double quotes around the arguments to the mv command are to guard against the spaces in the source directory and the destination directory (if any).

MikeG001 02-27-2011 07:38 AM

Dark_Helmet, Thank you.

This is slick......
Works like a charm....


mike g.


All times are GMT -5. The time now is 08:33 AM.