Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

I found this answer how do i... but it simply doesn't work - it did not rename any file for unknown to me reason

Before I started to search around I thought that it should be easy task even for novice penguin, but it doesn't seem so for me.

For example, I simply can't tell ls to list all *.txt in all subfolders, which was surprise to me (without grep or similar).
Then I found find and find . -name name_1.txt lists files fine, but

for f in $(find . -name name_1.txt) ; do echo "$f" ; done

splits whole file paths with space as separator, so it's unusable to pass that output to some command like mv or rename

I want to ask whats wrong with above command and if possible some nifty oneliner so I can recursively rename name_1.txt to name_2.txt

share|improve this question
    
What's the error you got when executing the command found in the other answer? Does any of your filenames contain a space in it? –  jcollado Nov 18 '11 at 12:45
    
I don't get error, but none file was renamed. Filename does not have spaces but subfolder names do. It's on Windows NTFS partition but that shouldn't change things I guess –  zetah Nov 18 '11 at 12:55
1  
I believe that's the reason it didn't work for you. The answer from @ams with find -print0 and xargs -0 uses the null character as a separator to avoid whitespace problems in the file names. –  jcollado Nov 18 '11 at 13:48
add comment

4 Answers 4

up vote 3 down vote accepted

find . -name '*.txt' -print0 | xargs -0 -n1 bash -c 'mv "$0" "${0/oldname/newname}"'

Obviously, that rename pattern is just a simple example, but beware that as is it will edit the whole path, not just the filename.

share|improve this answer
    
Thanks for you answer. As I don't get xargs logic (and your warning), could you just edit "${0/oldname/newname}" part with case where I want to rename all files named name_1.txt to name_2.txt recursively. Should it be "${0/name_1.txt/name_2.txt}"? –  zetah Nov 18 '11 at 12:57
    
I tried is on sample folder and it works as assumed in my previous comment –  zetah Nov 18 '11 at 13:15
1  
Let me break it down: print0 makes find output zero-terminated strings, so spaces are safe; -0 makes xargs expect zero-terminated input; -n1 says to invoke bash once for each file; and ${0/old/new} is bash syntax for find and replace in variable $0. Hope that helps. –  ams Nov 18 '11 at 15:14
    
It's sure interesting :) and new to me, and usable in future terminal sessions –  zetah Nov 18 '11 at 15:31
add comment

the mmv command will do this in a rather simple call:

mmv ";name_1.txt" "#1name_2.txt"

The ";" wildcard, which is reused in the replacement filename as "#1" matches also subdirectories.

If you need more complicated examples, you should look into the man-page.

share|improve this answer
    
Thanks, but I didn't tried it as it's currently not installed on my 11.04 and first answer worked fine. Cheers –  zetah Nov 18 '11 at 13:17
add comment

You could explore the plethora of choices available:

`$ apt-cache search rename | grep -i rename` 
share|improve this answer
add comment

As @ams points out, many recursive replace solutions require you to work with the whole path, not just the file name.

I use find's -execdir action to solve this problem. If you use -execdir instead of -exec, the specified command is run from the subdirectory containing the matched file. So, instead of passing the whole path to rename, it only passes ./filename. That makes it much easier to write the replace command.

find /the/path -type f \
               -name '*_one.txt' \
               -execdir rename 's/\.\/(.+)_one\.txt$/$1_two.txt/' {} \;

In detail:

  • -type f means only look for files, not directories
  • -name '*_one.txt' means means only match filenames that end in _one.txt
  • In this answer, I'm using the rename command instead of mv. rename uses a Perl regular expression to rename each file.
  • The backslashes after -type and -name are the bash line-continuation character. I use them to make this example more readable, but they are not needed in practice.
  • However, the backslash at the end of the -execdir line is required. It is there to esacpe the semicolon, which terminates the command run by -execdir. Fun!

Explanation of the regex:

  • s/ start of the regex
  • \.\/ match the leading ./ that -execdir passes in. Use \ to escape the . and / metacharacters
  • (.+) match the start of the filename. The parentheses capture the match for later use
  • _one\.txt match "_one.txt", escape the dot metacharacter
  • $ anchor the match at the end of the string
  • / marks the end of the "match" part of the regex, and the start of the "replace" part
  • $1 references the existing filename, because we captured it with parentheses. If you use multiple sets of parentheses in the "match" part, you can refer to them here using $2, $3, etc.
  • _two.txt the new file name will end in "_two.txt". No need to escape the dot metacharacter here in the "replace" section
  • / end of the regex

Before

tree --charset=ascii

|-- a_one.txt
|-- b_one.txt
|-- Not_this.txt
`-- dir1
    `-- c_one.txt

After

tree --charset=ascii

|-- a_two.txt
|-- b_two.txt
|-- Not_this.txt
`-- dir1
    `-- c_two.txt

Hint: rename's -n option is useful. It does a dry run and shows you what names it will change, but does not make any changes.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.