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
4 Answers
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.
4the 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.
1As @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 fmeans 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
renamecommand instead of mv. rename uses a Perl regular expression to rename each file. - The backslashes after
-typeand-nameare 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
-execdirline 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\.txtmatch "_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$1references 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.txtthe 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.txtAfter
tree --charset=ascii
|-- a_two.txt
|-- b_two.txt
|-- Not_this.txt
`-- dir1 `-- c_two.txtHint: 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.
You could explore the plethora of choices available:
`$ apt-cache search rename | grep -i rename`