views:

470

answers:

9

i have bunch of files that needs to be renamed.

file1.txt needs to be renamed to file1_file1.txt
file2.avi needs to be renamed to file2_file2.avi

as you can see i need the _ folowed by the original file name.

there are lot of these files.

+5  A: 
for file in file*.*
do 
    [ -f "$file" ] && echo mv "$file" "${file%%.*}_$file"
done

Idea for recursion

recurse() {
 for file in "$1"/*;do
    if [ -d "$file" ];then
        recurse "$file"
    else
        # check for relevant files
        # echo mv "$file" "${file%%.*}_$file"
    fi
 done
}
recurse /path/to/files
ghostdog74
I think you meant: for file in file*.* ; do mv "$file" \`echo "${file%%.*}_$file"\`; done
cmptrgeekken
Note: This doesn't distinguish between files and directories.
Mark Byers
@cmptrgeekken: I think he added the echo there so that the OP can test it before running it. Just remove the 'echo' to make it work.
Mark Byers
@Mark, not much effort adding a test for directory.
ghostdog74
@ghostdog: I agree, shell expansion *is* the best way to deal with poor file names, but how do you propose to do this ***recursively*** in bash 2.x/3.x? Because your code does not traverse into subdirectories.
SiegeX
@Siegex. you could do a recursion purely with the shell as i have illustrated in my edit. Its just the idea and not the FULL solution but you get the point.
ghostdog74
+1  A: 

I like the PERL cookbook's rename script for this. It may not be /bin/sh but you can do regular expression-like renames.

The /bin/sh method would be to use sed/cut/awk to alter each filename inside a for loop. If the directory is large you'd need to rely on xargs.

Epsilon Prime
A: 
#!/bin/bash
# Don't do this like I did:
# files=`ls ${1}`

for file in *.*
do
if [ -f $file ];
then
newname=${file%%.*}_${file}
mv $file $newname
fi
done

This one won't rename sub directories, only regular files.

James Morris
why the ls ?? you are going hit problem with spaces in file names..
ghostdog74
***Never*** use the output of `ls` for parsing. See http://mywiki.wooledge.org/ParsingLs for why that is
SiegeX
if you are not parsing for file names, it is still ok to parse ls for the other fields.
ghostdog74
Ok thanks for pointing that out. Edited.
James Morris
what does this mean `${file%%.*}`?
Vijay Sarathi
@benjamin button: "%%" removes the patern ".*" from the end of $file.
James Morris
+2  A: 
find . -type f | while read FN; do
  BFN=$(basename "$FN")
  NFN=${BFN%.*}_${BFN}
  echo "$BFN -> $NFN"
  mv "$FN" "$NFN"
done
Rob Curtis
This does not handle filenames with spaces or newlines. Also does not work with files in subdirectories as it moves the file in the subdirectory into the top-level directory.
SiegeX
+1  A: 

Many options. Look here

askvictor
A: 

One should mention the mmv tool, which is especially made for this.

It's described here: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/mass-rename.html

...along with alternatives.

Carl Smotricz
I think askvictor kinda beat ya to it there mate.
James Morris
Yeah, he was in such a hurry he didn't name the main tool from the bunch. I honestly independently found the same link searching for the tool I had in mind; and since I feel my answer is the more useful, I'm leaving it up.
Carl Smotricz
can you tell me which platform has mmv installed by default?
Yes: Rock Linux includes mmv; but that doesn't help much because it's a fairly obscure distro. However, Red Hat/Fedora/CentOS, Debian/Ubuntu and Gentoo all provide the package for easy installation.
Carl Smotricz
+1  A: 

For your specific case, you want to use mmv as follows:

pax> ll
total 0
drwxr-xr-x+ 2 allachan None 0 Dec 24 09:47 .
drwxrwxrwx+ 5 allachan None 0 Dec 24 09:39 ..
-rw-r--r--  1 allachan None 0 Dec 24 09:39 file1.txt
-rw-r--r--  1 allachan None 0 Dec 24 09:39 file2.avi

pax> mmv '*.*' '#1_#1.#2'

pax> ll
total 0
drwxr-xr-x+ 2 allachan None 0 Dec 24 09:47 .
drwxrwxrwx+ 5 allachan None 0 Dec 24 09:39 ..
-rw-r--r--  1 allachan None 0 Dec 24 09:39 file1_file1.txt
-rw-r--r--  1 allachan None 0 Dec 24 09:39 file2_file2.avi

You need to be aware that the wildcard matching is not greedy. That means that the file a.b.txt will be turned into a_a.b.txt, not a.b_a.b.txt.

The mmv program was installed as part of my CygWin but I had to

sudo apt-get install mmv

on my Ubuntu box to get it down. If it's not in you standard distribution, whatever package manager you're using will hopefully have it available.

If, for some reason, you're not permitted to install it, you'll have to use one of the other bash for-loop-type solutions shown in the other answers. I prefer the terseness of mmv myself but you may not have the option.

paxdiablo
+1  A: 

I use prename (perl based), which is included in various linux distributions. It works with regular expressions, so to say change all img_x.jpg to IMAGE_x.jpg you'd do

prename 's/img_/IMAGE_/' img*jpg

You can use the -n flag to preview changes without making any actual changes.

prename man entry

Alex JL
A: 

So far all the answers given either:

  1. Require some non-portable tool
  2. Break horribly with filenames containing spaces or newlines
  3. Is not recursive, i.e. does not descend into sub-directories

These two scripts solve all of those problems.

Bash 2.X/3.X

#!/bin/bash

while IFS= read -r -d $'\0' file; do
    dirname="${file%/*}/"
    basename="${file:${#dirname}}"
    echo mv "$file" "$dirname${basename%.*}_$basename"
done < <(find . -type f -print0)

Bash 4.X

#!/bin/bash

shopt -s globstar
for file in ./**; do 
    if [[ -f "$file" ]]; then
        dirname="${file%/*}/"
        basename="${file:${#dirname}}"
        echo mv "$file" "$dirname${basename%.*}_$basename"
    fi
done

Be sure to remove the echo from whichever script you choose once you are satisfied with it's output and run it again

Edit

Fixed problem in previous version that did not properly handle path names.

SiegeX
i tested your bash2.x/3.x solution. example of my output--> mv ./file2.txt _./file2.txt
it also didn't work with files with newlines.
using shell expansion is still the best approach, even for newlines in file names
ghostdog74
@levislevis85: fixed the problem with the pathname but it *does* work with newlines, it's just that the `echo` makes you think otherwise.
SiegeX
It's good that you came up with a solution that does all of those things... but note that the question was asked specifically about files in 1 directory, on Linux, with nothing unusual about the filenames.
Alex JL
@Code Duck: Adding recursive capability was a trivial task and one should *never* assume anything about filenames, *always* treat them as if they include newlines or other nasties. The second you do not is when somebody adds one that breaks your script horribly and does god knows what.
SiegeX