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.
find -print0
andxargs -0
uses the null character as a separator to avoid whitespace problems in the file names.