You can use the Perl rename command file-rename
for this. If you're unfamiliar with this tool, I recommend you run file-rename --version
to see if you have it--any output other than an error means you do. A few Ubuntu releases don't come with it by default, so you might not have it, in which case you can get it by installing the rename
package. (The "command not found" error if you don't have it will likely even tell you about that.)
cd
to the abc
directory whose subdirectories contain the files you want to rename and run this. Then if the renaming operations it shows are really the ones you want, run it again without -n
to do the actual renaming.
file-rename -n 's|^(.+)_(\d+)/ghi\.jpg$|$1_$2/$1_xyz_$2.jpg|' */ghi.jpg
This generates the list of files to rename using a shell glob and passes it as arguments to file-rename
to be renamed. It assumes that you want:
- To rename files called exactly
ghi.jpg
that reside directly in subdirectories--named ending in _
followed by digits--of the directory abc
.
- Each file's new name to be its containing directory without the trailing underscore and digits, followed by
xyz
, followed the trailing underscore and digits from its containing directory's name.
The way that works is that s|pattern|substitution|
operates on files whose paths, as they were passed as arguments to file-rename
, match pattern
(interpreted as a regular expression in the Perl dialect). The operation it performs is to rename them using the specified substitution
. Files whose names didn't contain a match to pattern
wouldn't be renamed. There's nothing special about |
as the choice of separator; I just don't want to use the usual /
since that's appearing in the paths.
In the pattern ^(.+)_(\d+)/ghi\.jpg$
:
^
matches the beginning of the path and $
matches the end. This allows only matches on the entirety of each path passed in, rather than just any part of it.
(To address one possible point of confusion: the paths being passed in are still relative paths like def_0/ghi.jpg
, not absolute paths like /home/you/abc/def_0/ghi.jpg
. When people talk about "full paths," they typically mean absolute paths. By "the entirety of the path" I just mean each whole argument passed in rather than a smaller substring of it.)
(.+)
matches and captures ((
)
) a sequence of one or more (+
) of any character (.
). This goes into the first capture group.
_
matches itself, a literal underscore character.
(\d+)
matches and captures ((
)
) a sequence of one or more (+
) digits (\d
). This goes into the second capture group.
/
matches itself, a literal slash character. (It doesn't behave as a delimiter because we used |
as a delimiter instead.)
ghi
matches the literal text ghi
.
\.
matches just a .
character. The \
escapes the .
; otherwise, the .
would mean "any character."
jpg
matches the literal text jpg
.
The substitution $1_$2/$1_xyz_$2.jpg
signifies:
- Text that was placed in the first capture group (
$1
). This is the text that matched .+
in the pattern.
- An underscore,
_
.
- Text that was placed in the second capture group (
$2
). This is the text that matched \d+
in the pattern.
- A slash,
/
.
- The first capture group (
$1
) again.
- The text
_xyz_
.
- The second capture group (
$2
) again.
- The dot,
.
. Unlike in patterns, in substitutions a .
is not treated specially, so it need not (and should not) be escaped with an \
.
- The text
jpg
.
For general documentation the use of file-rename
, see the output of file-rename --help
, man file-rename
, and this blog post. (I use the file-rename
instead of the rename
command, because on some systems rename
is another utility. On most they are the same.)
0
in filenames appeared where you seemed to mean10
. Otherwise, for example, you would have listedabc/def_0/ghi.jpg
twice.