Before declaring this a duplicate, consider that I'd need this for a specific reason: batch renaming (or copying to a new name) of a tree structure that contains a common string in file and directory names. Here's an example (tried on Ubuntu 14.04, so GNU tools):
cd /tmp
mkdir myproj
mkdir -p myproj/myproj_AA/myproj_BB
touch myproj/myproj_AA/myproj_BB/myproj_CC.dat
mkdir myproj/myproj_AA/myproj_DD
touch myproj/myproj_AA/myproj_DD/myproj_EE.dat
mkdir -p myproj/myproj_XX/myproj_YY
touch myproj/myproj_XX/myproj_YY/myproj_ZZ.dat
mkdir -p myproj/myproj_XX/myproj_WW
touch myproj/myproj_XX/myproj_WW/myproj_QQ.dat
tree myproj # to visualise
This directory structure's tree
looks like this:
myproj
├── myproj_AA
│ ├── myproj_BB
│ │ └── myproj_CC.dat
│ └── myproj_DD
│ └── myproj_EE.dat
└── myproj_XX
├── myproj_WW
│ └── myproj_QQ.dat
└── myproj_YY
└── myproj_ZZ.dat
6 directories, 4 files
So, I'd want all the entries in myproj/
, including myproj
itself, renamed to myTESTproj
instead of myproj
(wherever it may occur as a name). So, first I need to obtain a listing with relative paths in respect to the current directory - and then I need to have it sorted such that the outermost children (I think this is equivalent to files with the longest relative pathnames, but not sure) are first (because if I rename/mv the directory first, and then try to rename a file in it, it will likely use the old dir name as first argument, and fail since the name is now changed).
I'm aware there is ls -R --group-directories-first myproj/
to use ls
recursively and group directories first, but its output is like this:
$ ls -R --group-directories-first myproj/
myproj/:
myproj_AA myproj_XX
myproj/myproj_AA:
myproj_BB myproj_DD
myproj/myproj_AA/myproj_BB:
myproj_CC.dat
myproj/myproj_AA/myproj_DD:
myproj_EE.dat
myproj/myproj_XX:
myproj_WW myproj_YY
myproj/myproj_XX/myproj_WW:
myproj_QQ.dat
myproj/myproj_XX/myproj_YY:
myproj_ZZ.dat
... that is, it is not a plain list with subpaths, that I could easily feed to while read f; do ...
Closest I came to is to use find
instead:
$ find myproj/
myproj/
myproj/myproj_AA
myproj/myproj_AA/myproj_DD
myproj/myproj_AA/myproj_DD/myproj_EE.dat
myproj/myproj_AA/myproj_BB
myproj/myproj_AA/myproj_BB/myproj_CC.dat
myproj/myproj_XX
myproj/myproj_XX/myproj_YY
myproj/myproj_XX/myproj_YY/myproj_ZZ.dat
myproj/myproj_XX/myproj_WW
myproj/myproj_XX/myproj_WW/myproj_QQ.dat
So, here I do have a plain list of subpaths, however it is sorted root node first towards leaf nodes - and I need leaf nodes first. And I'm trying stuff like find myproj/ | sort -n
, but it seems to make no difference. So if I do something like:
$ find myproj/ | sort -n | while read f; do mv -v $f $(echo $f | sed 's/myproj/myTESTproj/g'); done
‘myproj/’ -> ‘myTESTproj/’
mv: cannot stat ‘myproj/myproj_AA’: No such file or directory
mv: cannot stat ‘myproj/myproj_AA/myproj_BB’: No such file or directory
mv: cannot stat ‘myproj/myproj_AA/myproj_BB/myproj_CC.dat’: No such file or directory
...
... then the intended recursive rename fails immediately, as the root node (directory) is renamed first, and thus all further references to it are invalid.
So, how can I obtain a proper recursive listing of a subdirectory with leaf nodes first, to use it in a batch rename like this?