2

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.

1 Answers1

1

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:

Raffa
  • 34,963