Prepend user-defined string to all files and folders recursively using find and rename.
I’d like to prepend “x “ (no quotes) to a directory and all of its contents down through all subdirectories. I’m a beginner using macOS Mojave 10.14.6 and Terminal. I downloaded rename using Homebrew for this purpose.
Example:
/Old Project
/Old Project/Abstract.rtf
/Old Project/Manuscript.docx
/Old Project/Data Analysis
/Old Project/Data Analysis/Working Syntax.sps
/Old Project/Data Analysis/Working Data.sav
/Old Project/Data Analysis/Cleaned Data.sav
/Old Project/Data Analysis/Figures
/Old Project/Data Analysis/Figures/Figure 1.png
/Old Project/Data Analysis/Figures/Figure 2.png
/Old Project/Data Analysis/Raw Data
/Old Project/Data Analysis/Raw Data/2020-06-26.csv
/Old Project/Ethics
/Old Project/Ethics/Application.pdf
/Old Project/Ethics/Approval.pdf
/Old Project/Ethics/Informed Consent.docx
Desired Result:
/x Old Project
/x Old Project/x Abstract.rtf
/x Old Project/x Manuscript.docx
/x Old Project/x Data Analysis
/x Old Project/x Data Analysis/x Working Syntax.sps
/x Old Project/x Data Analysis/x Working Data.sav
/x Old Project/x Data Analysis/x Cleaned Data.sav
/x Old Project/x Data Analysis/x Figures
/x Old Project/x Data Analysis/x Figures/x Figure 1.png
/x Old Project/x Data Analysis/x Figures/x Figure 2.png
/x Old Project/x Data Analysis/x Raw Data
/x Old Project/x Data Analysis/x Raw Data/x 2020-06-26.csv
/x Old Project/x Ethics
/x Old Project/x Ethics/x Application.pdf
/x Old Project/x Ethics/x Approval.pdf
/x Old Project/x Ethics/x Informed Consent.docx
What I Have So Far:
find . -depth (-execdir OR -exec) rename -n ’s/^/x /‘ {} +
find .
List all files and directories recursively within the current working directory. Will output a list of filenames that include the path.
-depth
Directs find to start at the lowest depth (at the bottom of the subdirectories) so you don’t run into the problem whereby an un-renamed file in a renamed directory cannot be found because that path no longer exists. (How do I get this find and rename command to work with subdirectories?)
-exec
Find will execute the named command (rename) on each item in the list.
-execdir
Find will execute the named command (rename) on each item in the list, with one difference - it will first enter each subdirectory then pass only the filename to the rename command (no path).
rename
Rename command that uses Perl regular expressions. It cannot handle recursive file renaming on its own, which is why it needs find. Apparently it is a standard command on some systems while there’s another rename command that is standard on other systems, leading to some confusion.
-n
Directs rename to show what will happen and not actually run it.
’s///‘
Tells rename to do a substitution, with the first section replaced by the second section. In my syntax (’s/^/x /‘
) to replace ^ (marker for the beginning of the filename) with x .
{}
Directs rename to the list of files from find.
+
Tells find the command is over.
-exec
versus -execdir
-exec
passes along the full file path. Rename acts upon the full file path as outlined in the documentation for rename and in the answer to a similar question:
“Note that rename will then operate on the entire path, not just the filename.” (http://plasmasturm.org/code/rename/)
“Temporary note: there is something wrong - the rename pattern does not handle filenames with >path; I'm working on a fix” (https://unix.stackexchange.com/a/153489)
So, if I use -exec
, then I would get "x /Old Project/Data Analysis/Figures/Figure 1.png" instead of "/Old Project/Data Analysis/Figures/x Figure 1.png", for example. To solve this, I believe I would have to write a complex regular expression to somehow capture just the filename portion as outlined in this answer to a similar question:
“If you only want to modify the last component, you can anchor your regexp at
(\A|?<=/)
, and make sure that it doesn't match any / and only matches at the last /.” (https://unix.stackexchange.com/a/166886)
I tried the regular expression given in this answer, but it resulted in an error (“Quantier follows nothing in regex…”) and I’m not actually sure it is for my version of rename.
execdir
passes along the only the file name, which is promising. In fact, when I dry run the command, all the planned changes look perfect. However, the actual result is not - it renames files and folders in the main directory but fails to find all other files and folders. It says that they do not exist.
I eventually found this answer:
“find -execdir | rename
This would be the best way to do it if it weren't for the relative path madness, as it avoids Perl regex fu to only act on the basename:
PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
find a -depth -execdir rename 's/(.*)/\L$1/' '{}' \;
-execdir first cds into the directory before executing only on the basename.Unfortunately, I can't get rid of that PATH hacking part, find -execdir refuses to do anything if you have a relative path in PATH…” (Lowercasing all directories under a directory)
So, as I understand it, the command works in theory, which is why it works in the dry run, but, in practice, find
refuses to actually go into each subdirectory for the rename command.
My Questions:
- For using
exec
: Is there a way to isolate the filename from the full file path for rename? - For using
execdir
: Is there a way to ask find to use or to get absolute path names?
Notes
I'm very new to programming.
I found this very thorough answer (https://stackoverflow.com/a/54163971/13821837) but the syntax doesn't match what works for my system.