Score:2

How to batch rename files (images) recursively based on CSV file

cz flag

before writing this post, I obviously searched the internet and found several solutions that could perhaps work. But I'm a complete newbie to command line programming or writing.

What I have : Thousands of image files stored in a parent directory with multiple sub-directories.

What I want to do: Rename all of these files including those in the sub-directories using a CSV file with a comma separator. A typical row looks like this :

IMG_20221207_094623.jpg,photo_1.jpg
IMG_20221207_094627.jpg,photo_2.jpg
IMG_20221207_094651.jpg,photo_3.jpg

I found a post on this forum describing a command line using the sed function.

How to batch rename files (images) based on CSV file

To the almost identical question asked there (except for the sub-directories part), the following command line was proposed by @terdon

sed 's/"//g' files.csv | while IFS=, read orig new; do mv "$orig" "$new"; done

The solution provided by @terdon works fine for files in the current directory but does not process files in sub-directories, unless I haven't figured out how.

The return from the command line is as follows:

mv: cannot stat '20221207_094623 .jpg': No such file or directory

And if I try to add in my csv file in the origin column the full path of the file, I have this return

mv: cannot stat '/home/antoine.delauney/Images/00-Visite/20221207_094623.jpg': No such file or directory

For the first part of the {'s/"//g'} command, my file doesn't have quotes so I don't need to strip them like this sequence does. But I wasn't able to edit that first sequence without that the command line fails. If someone could guide me on this first part it would be very nice because I sometimes have to process batches of image files that are in a single directory.

However, the central question of this post is whether the sed command, directly or possibly with an additional command, is able to process a batch of files recursively by going to explore all the sub-directories from the given target? It will be very helpful !

Thank you in advance.

PS : I'm really sorry if my English doesn't always use the right terms. I am French, I speak a little English but for the purposes of this post I greatly helped myself from Google translate. There may therefore be inappropriate or unused terms (especially for examples of command line errors). My excuses.

terdon avatar
cn flag
Just add the path to your list of files. Instead of `file.csv`, if the file is in a subdirectory, put `subdirectory/file.csv` in your `files.csv`.
djinnroh avatar
cz flag
I'm not sure I understood or was clear. My image files can both be in the parent directory or in sub-directories which can themselves contain sub-directories. My need is to successfully process all files at once starting from my csv file. I've tried to add the complete path like this /home/user/Images/00-Visite/A/D/IMG_20221207_095422.jpg , photo_1.jpg with the wrong result mention in my post. Do I need to add quote type characters for this to work? My csv file is in the parent directory 00-Visite. I don't know if I'm clear enough.@terdon
terdon avatar
cn flag
Ah no, you would need the full path on both sides: `/home/user/Images/00-Visite/A/D/IMG_20221207_095422.jpg /home/user/Images/00-Visite/A/D/photo_1.jpg`.
djinnroh avatar
cz flag
So based on your answer and as my working methodology allows me to have the complete and corresponding list of full paths for each file. I was able to test it by putting the path on both sides. And it worked well. However, I have a question that is still unresolved. As I said after the original post, I don't need to replace quotes since my file doesn't contain any. How can I modify the first sequence of the command while keeping the command line functional? Thanks @terdon
terdon avatar
cn flag
You don't need to, the sed will just print the lines of the file unchanged if there are no quotes so you can use it as is. Alternatively, you could replace `sed '...' file` with `cat file`, but even better, just feed the file into the loop directly like this: `while IFS=, read orig new; do mv "$orig" "$new"; done < file.csv`.
djinnroh avatar
cz flag
Thanks @terdon for these elements. I'm going to try it on my files.
Score:1
jp flag

From the parent directory, you can do:

while IFS=, read -r orig new; do find -type f -name "$orig" -execdir echo mv -n -- {} "$new" \; ; done < files.csv

echo is for dry-run ... remove it to do the actual renaming.

The above one-liner is the same as this multi-liner:

while IFS=, read -r orig new; do
  find -type f -name "$orig" -execdir echo mv -n -- {} "$new" \;
done < files.csv

Explanation:

In the shell(i.e. bash), You can read a file line by line with bash's builtin read command with its option -r to preserve and pass backslashes i.e. \ to the body of the loop instead of interpreting them into escapes and consuming immediately in the head of the loop which is extremely important when dealing with filenames in particular ... The file can be fed to the loop using the shell's input redirection with < e.g. like so:

while IFS=, read -r orig new; do
  # Some command/s here
done < file

Depending on the data structure in your file, each line will be split by the shell's word splitting into fields based on the value of the internal field separator IFS and you might want to change it from the default to the actual field separator/delimiter used in your data structure e.g. IFS=, in your case will split fields on , and not the default space/tab/(newline) ... The shell will assign the first(from the left) field to the first passed parameter i.e. orig and the second(or the rest of the fields if there are more fields than the total of passed parameters) field to the second one i.e. new ... So the loop will begin with the two variables $orig and $new consecutively holding the two fields from the first line in your file i.e. the original filename e.g. IMG_20221207_094623.jpg and the new filename e.g. photo_1.jpg ... These variables are then available inside the body of the loop ready to be expanded and used ... Inside the body of the loop, the following command:

find -type f -name "$orig" -execdir mv -n -- {} "$new" \;

Will translate to:

find -type f -name "IMG_20221207_094623.jpg" -execdir mv -n -- {} "photo_1.jpg" \;

That is ... find will search the current directory and all its sub-directories for -type f(files only) and -name "IMG_20221207_094623.jpg"(exact filename case-sensitive) then pass the search result/s to find's action -execdir(which will run the command i.e. mv from the sub-directory containing the matched file) ... Thus renaming any file named exactly IMG_20221207_094623.jpg to photo_1.jpg in the current working directory(this is the default search path for find and you can also specify it if you want with . e.g. find . -type f ...) and all its sub-directories ... The {} is a placeholder for the finds output/result specifying where in the order of arguments it should be placed and processed … Notice that the \; is needed to indicate the end of -execdir's arguments ... Notice also that the mv's option -n is a good safety measure as it will prevent overwriting per-existing files with the same filename and -- will indicate the end of options so that mv will not choke on filenames starting with a dash - ... After all the commands in the loops body(i.e. between the two command grouping constructs do ... done) execute, then the loop's head(i.e. while ....) will execute reading the next line from your file and the same process is repeated again and again until all lines in your file are read and the loop will then exit.

Notice:

You can tell mv to print what it's doing(when it modifies files) by using the option -v with it like so:

find -type f -name "$orig" -execdir mv -v -n -- {} "$new" \;

And you can redirect the output to a log file e.g. files.log like so:

find -type f -name "$orig" -execdir mv -v -n -- {} "$new" >> files.log \;

Related information:

djinnroh avatar
cz flag
This work just fine. Thanks so much. For my own knowledge, would it be too much to ask to have an explanation of the different parts of the command line? @Raffa
djinnroh avatar
cz flag
And follow-up question. As we work with several cameras of the same model on which we cannot modify the structure of the file name, it can happen that we have several times the same file name on the total volume of image files. Could this create a conflict or rename a duplicate with the wrong destination name? Is it possible in the csv file with original name and new name to indicate the full path to avoid this type of conflict/error and if so in a particular form? @Raffa
Raffa avatar
jp flag
@djinnroh "would it be too much to ask to have an explanation of the different parts of the command line?" ... No, not at all ... I updated the answer with that ... If you find I missed a detail, please let me know and I'll include it ... I am glad that helped ... And no, having duplicate lines in your `files.csv` will not be a problem and will have no effect as the files with the same original name will have been already renamed from the first matching line and thus `find` will return no results so nothing to be renamed again :-)
djinnroh avatar
cz flag
Sorry, I'm going back to identical filenames. These relate to different topics. How can I be sure that the first file encountered will be the one that corresponds to the desired name (understood in terms of full access path). Let's say the destination file is formatted to contain the letter B. If on two duplicate files, one must end with a letter A and the other with the letter B, how can I be sure that the script does not reverse both.
djinnroh avatar
cz flag
and I completely forgot, but again a big thank you for all the explanations. @Raffa
Raffa avatar
jp flag
@djinnroh "*how can I be sure ...*" ... I updated the answer with a logging mechanism for every change to any file made by the `mv` command when it's made(*i.e. no new log lines ... means no changes were made*)
djinnroh avatar
cz flag
@ Raffa Great ! I am really grateful to you for all these explanations and the time spent. Thanks ! Between your solution and that of @terdon, I have no more excuse not to succeed
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.