递归地列出目录,首先包含子路径和叶节点(文件)(用于批量重命名部分文件名)?

递归地列出目录,首先包含子路径和叶节点(文件)(用于批量重命名部分文件名)?

在声明它重复之前,请考虑我需要它是出于特定原因:批量重命名(或复制到新名称)包含文件和目录名称中的公共字符串的树结构。这是一个示例(在 Ubuntu 14.04 上尝试过,因此使用 GNU 工具):

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

该目录结构tree如下所示:

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

因此,我希望 中的所有条目myproj/(包括myproj其本身)重命名为myTESTproj而不是myproj(无论它作为名称出现在哪里)。因此,首先我需要获得一个包含相对于当前目录的相对路径的列表 - 然后我需要对其进行排序,以便最外面的子目录(我认为这相当于具有最长相对路径名的文件,但不确定)是第一个(因为如果我先重命名 /mv 目录,然后尝试重命名其中的文件,它可能会使用旧的目录名称作为第一个参数,并且由于名称现在已更改而失败)。

我知道首先ls -R --group-directories-first myproj/要递归使用ls和分组目录,但它的输出是这样的:

$ 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

...也就是说,它不是一个带有子路径的简单列表,我可以轻松地将其提供给while read f; do ...

我最接近的是使用find

$ 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

因此,这里我确实有一个简单的子路径列表,但是它是按照从根节点到叶节点的顺序排序的 - 而我需要先排序叶节点。我正在尝试类似的东西find myproj/ | sort -n,但似乎没有什么区别。所以如果我做类似的事情:

$ 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
...

...然后预期的递归重命名立即失败,因为根节点(目录)首先被重命名,因此对它的所有进一步引用都是无效的。

那么,如何首先获得具有叶节点的子目录的正确递归列表,以便在像这样的批量重命名中使用它?

答案1

如果您的目标只是重命名,那么在目录本身之前处理每个目录的内容还不够吗,也就是说,您不需要全部叶子(来自全部目录)首先?find -depth正是这样做的。

$ mkdir -p a/b c/d
$ find -depth
./a/b
./a
./c/d
./c
.

然后你可以使用find -execBash 重命名文件:

$ find -depth ! -name . -name "*myproj*" -execdir bash -c '
    for f; do mv "$f" "${f/myproj/myTESTproj}" ; done' bash {} +

答案2

如果您安装了该命令的 Perl 版本rename(有时称为prename),这将适合您

find myproj -depth -name '*myproj*' -exec rename -n 's!(.*)myproj!$1myTESTproj!' {} +

-depth选项可find确保任何目录中的子目录都列在目录本身之前。操作+的后缀允许-exec{}指定命令的单次调用进行多次插入。以降低效率为代价,您可以将其替换为\;.

当您确定它会执行您想要的操作时,请删除-n或将其替换为-v

答案3

一旦我发布了问题,我就记住了要寻找的内容 -如果叶节点是那些具有最长相对路径名的节点(我不确定它是否总是正确的,但似乎至少在OP示例中是这样),那么我们只需要一种方法来按字符串长度对字符串列表进行排序;不幸的是sort似乎没有这样的选择。

但是,我发现https://stackoverflow.com/questions/5917576/sort-a-text-file-by-line-length-include-spaces- 然后从那里选择perl解决方案:

$ find myproj/ | perl -e 'print sort { length($b) <=> length($a) } <>'
myproj/myproj_AA/myproj_DD/myproj_EE.dat
myproj/myproj_AA/myproj_BB/myproj_CC.dat
myproj/myproj_XX/myproj_YY/myproj_ZZ.dat
myproj/myproj_XX/myproj_WW/myproj_QQ.dat
myproj/myproj_AA/myproj_DD
myproj/myproj_AA/myproj_BB
myproj/myproj_XX/myproj_YY
myproj/myproj_XX/myproj_WW
myproj/myproj_AA
myproj/myproj_XX
myproj/

然而,简单的sed 's/myproj/myTESTproj/g'替换在这里也不起作用:

$ find myproj/ | perl -e 'print sort { length($b) <=> length($a) } <>' \
> | while read f; do mv -v $f $(echo $f | sed 's/myproj/myTESTproj/g'); done
‘myproj/myproj_AA/myproj_DD/myproj_EE.dat’ -> ‘myTESTproj/myTESTproj_AA/myTESTproj_DD/myTESTproj_EE.dat’
mv: cannot move ‘myproj/myproj_AA/myproj_DD/myproj_EE.dat’ to ‘myTESTproj/myTESTproj_AA/myTESTproj_DD/myTESTproj_EE.dat’: No such file or directory
...

...所以我们需要一个sed 仅替换一行中的最后一个匹配项,即sed -E 's/(.*)myproj/\1myTESTproj/g'

$ find myproj/ | perl -e 'print sort { length($b) <=> length($a) } <>' \
| while read f; do mv -v $f $(echo $f | sed -E 's/(.*)myproj/\1myTESTproj/g'); done
‘myproj/myproj_AA/myproj_DD/myproj_EE.dat’ -> ‘myproj/myproj_AA/myproj_DD/myTESTproj_EE.dat’
‘myproj/myproj_AA/myproj_BB/myproj_CC.dat’ -> ‘myproj/myproj_AA/myproj_BB/myTESTproj_CC.dat’
‘myproj/myproj_XX/myproj_YY/myproj_ZZ.dat’ -> ‘myproj/myproj_XX/myproj_YY/myTESTproj_ZZ.dat’
‘myproj/myproj_XX/myproj_WW/myproj_QQ.dat’ -> ‘myproj/myproj_XX/myproj_WW/myTESTproj_QQ.dat’
‘myproj/myproj_AA/myproj_DD’ -> ‘myproj/myproj_AA/myTESTproj_DD’
‘myproj/myproj_AA/myproj_BB’ -> ‘myproj/myproj_AA/myTESTproj_BB’
‘myproj/myproj_XX/myproj_YY’ -> ‘myproj/myproj_XX/myTESTproj_YY’
‘myproj/myproj_XX/myproj_WW’ -> ‘myproj/myproj_XX/myTESTproj_WW’
‘myproj/myproj_AA’ -> ‘myproj/myTESTproj_AA’
‘myproj/myproj_XX’ -> ‘myproj/myTESTproj_XX’
‘myproj/’ -> ‘myTESTproj/’
$ tree myTESTproj/
myTESTproj/
├── myTESTproj_AA
│   ├── myTESTproj_BB
│   │   └── myTESTproj_CC.dat
│   └── myTESTproj_DD
│       └── myTESTproj_EE.dat
└── myTESTproj_XX
    ├── myTESTproj_WW
    │   └── myTESTproj_QQ.dat
    └── myTESTproj_YY
        └── myTESTproj_ZZ.dat

6 directories, 4 files

我想这符合我的要求 - 但是,我不确定最长路径名 == 叶文件节点的假设是否始终正确;即使是 - 有没有更简单的方法来做到这一点?


编辑:在这样的结构的情况下这肯定会失败:

myproj/somespecdir/someotherdir/myproj_CC.dat
myproj/myproj_AA/myproj_DD/myproj_EE.dat
myproj/somespecdir/someotherdir
myproj/myproj_AA/myproj_DD
myproj/somespecdir
myproj/myproj_AA
myproj/

...也就是说,如果重命名路径中要搜索和替换的子字符串的第一次出现也是最后一个(唯一的);并且它出现在列表中多次出现该子字符串的路径之前。

相关内容