在 Bash 中使用单行重命名目录及其子目录

Posted

技术标签:

【中文标题】在 Bash 中使用单行重命名目录及其子目录【英文标题】:Rename a directory and its sub-directory with a one liner in Bash 【发布时间】:2019-10-13 10:18:42 【问题描述】:

我有一个我反编译的 android 应用程序,我正在编写一个脚本来重命名目录。

反编译文件夹里面是路径:

smali/com/FOLDER/SUB-FOLDER

我想将现有文件夹重命名为:

smali/com/NEW-NAME/SUB-NEW-NAME

我在想我可以使用 mv 命令,但在手册页中挖掘它似乎无法完成。我正在尝试这个:

mv smali/com/FOLDER smali/com/FOLDER/SUB-FOLDER -t smali/com/NEW-NAME/SUB-NEW-NAME

这仅适用于将多个文件移动到单个目的地。

编辑:

现有文件夹:

  smali/com/FOLDER/SUB-FOLDER 

我正在尝试重命名这两个文件夹,同时保留每个文件夹的内容。

一个一个地重命名每个文件夹的例子是:

mv smali/com/FOLDER/ smali/com/NEW-NAME
mv smali/com/NEW-NAME/SUB-FOLDER smali/com/NEW-NAME/SUB-NEW-NAME

【问题讨论】:

unix.stackexchange.com/q/189253/273492 Is there a way to make mv create the directory to be moved to if it doesn't exist?的可能重复 文件夹已经存在并且每个文件夹都有内容我只想重命名文件夹 “单线”?除非您有令人信服的技术原因,否则我认为该限制将这个问题排除在本网站所需的“实用”标准之外。将简洁性优先于正确性或可读性适用于打代码高尔夫(如 Code Golf 所做的那样),而不是实际用例。 "只想重命名文件夹" -- 那么,smali/com/FOLDER/OTHER-NAME 是否应该更改为 smali/com/NEW-NAME/OTHER-NAME?您说的是“文件夹”,复数形式,如果您将 FOLDER 重命名为 NEW-NAME,则预计会出现 OTHER-NAME。如果你希望FOLDERNEW-NAME 同时存在(所以OTHER-NAME 仍然可以留在FOLDER),那么你不能重命名existing OTHER-NAME,你必须创建一个 new OTHER-NAME. 【参考方案1】:

这是一个微不足道的单行;例如:

rename_pieces smali/com/FOLDER/SUB-FOLDER smali/com/NEW/SUBNEW

...当然,假设您首先运行适当的函数定义。如果您只想重命名SUB-FOLDER,则在创建NEW-NAME 后如果它不存在,则如下所示:

rename_pieces() 
  local old=$1 new=$2
  [[ $new = */* ]] && mkdir -p -- "$new%/*"
  mv -T -- "$old" "$new"

...与任何其他解释相比,这更可能是您真正想要的行为,只要它留下FOLDER 的内容 SUB-FOLDER 仅以其原始名称。


相比之下,如果您真的想要重命名这两个目录,那就更有趣了。如果我们保证源和目标的深度相同,则可能如下所示:

log_or_run() 
  if [[ $log_only ]]; then   # just log what we would run
    printf '%q ' "$@" >&2    # use printf %q to generate a safely-escaped version
    printf '\n' >&2          # ...and terminate with a newline.
  else
    "$@"                     # actually run the command
  fi

rename_pieces() 
  local old=$1 new=$2 common
  while [[ $old%%/* = "$new%%/*" ]]; do
    common+=/"$old%%/*"
    old=$old#*/; new=$new#*/
  done
  while [[ $old && $new ]]; do
    log_or_run mv -T -- "$common#//$old%%/*" "$common#//$new%%/*"; echo
    common+=/"$new%%/*"
    [[ $old = */* && $new = */* ]] || return
    old=$old#*/; new=$new#*/
  done

之后:

log_only=1 rename_pieces smali/com/FOLDER/SUB-FOLDER smali/com/NEW/SUBNEW

...输出时发射:

mv -T -- smali/com/FOLDER smali/com/NEW
mv -T -- smali/com/NEW/SUB-FOLDER smali/com/NEW/SUBNEW

...在没有log_only=1 的情况下执行同样的操作实际上会运行这些命令。

【讨论】:

【参考方案2】:

想不出更优雅的方法,但以下应该可以: find . -path '.*com/FOLDER/SUBFOLDER' | awk 'new_dir=$1; sub("/FOLDER/SUBFOLDER","/NEW/SUBNEW", $new_dir); printf("mkdir -p %s\nmv %s %s\n", $new_dir, $1, $new_dir);' > cmds

这会在cmds 文件中获取所有必需的mv 命令。验证它是否正确,然后使用sh cmds 执行。虽然这不是最有趣的,但在处理重要数据时我喜欢这种方法,因为验证您将要运行的内容总是更好。

编辑:修复了 cmets 中指出的脚本不存在时不创建目录的问题。

【讨论】:

从安全角度来看,这是一个可怕的想法;它 需要 进行验证,因为您正在生成一个字符串,并且该字符串可能具有被解析为语法的内容。相比之下,常规的 shell 扩展 被解析为语法,因此无论它们的内容是什么,它们都可以安全运行(至少,在它们不能运行完全不相关的命令、重定向的范围内是安全的)等)。 例如:mv -- "$old" "$new" 可以被信任除了mv 之外永远不会运行任何命令,即使new=$'$(rm -rf ~)\'$(rm -rf ~)\'' 也是如此。但是,一旦您使用awk 生成该mv 命令并通过shell 运行它,这些保证就会完全消失,因为您将数据解析为代码,否则它将永远无法成为数据以外的任何东西。 此外,这实际上不起作用。它生成一个命令mv ./smali/com/FOLDER/SUBFOLDER ./smali/com/NEW/SUBNEW,但该命令将失败,因为smali/com/NEW 不存在。 @CharlesDuffy 好吧,这就是为什么您需要验证 cmds 文件中生成的内容。我同意生成子目录的失误,但这应该很容易通过前缀 mv 来解决。你的答案肯定是更“正确”的答案,但如果我必须做一些快速、临时和肮脏的事情,这就是我会做的。 Err -- 如果你mkdir new_dir,那么mv something new-dir 将创建new-dir/something;所以在这种情况下,你最终会得到/NEW/SUBNEW/FOLDER 而不是/NEW/SUBNEW。此外,在 cmets 中,OP 非常清楚这个问题(诚然,为了清楚起见,我花了很多时间试图按下它们)他们确实想要重命名 both 目录,这样@ 987654338@ 变为 /NEW-NAME/something-else。如果不是这样,这个问题将是重复的。【参考方案3】:

好吧,我会咬人的……

如果你想要的只是一条线,你可以随时使用AND_IF 命令&& 将多条线合二为一。

mv ./FOLDER ./NEW-NAME && mv ./NEW-NAME/SUB-FOLDER ./NEW-NAME/SUB-NEW-NAME

这增加了子文件夹重命名只有在父重命名成功时才会发生的附加效果。

但是,首先移动子文件夹不是更有意义吗?让我们为 from 标签和 $t 分配两个变量 $f 。这些可以放在一条线上,但是当我们想再次更改内容时,这种方式更容易。

f=FOLDER; t=NEW-NAME;
mv "./$f/SUB-$f" "./$f/SUB-$t" && mv "./$f" "./$t"

所有剩余的例子都假设设置了 from 和 to 变量。

让我们将$p 分配给父查找并将$s 分配给所有匹配的子文件夹,以确保它们都存在。这个例子和上面所有的例子仍然是纯 BASH 解决方案。

p=(./*$f); [[ -d $p ]] && for s in $p/*$f; do mv "$s" "$s%$f$t"; done && mv "$p" "$p%$f$t"

要考虑的另一种选择是使用find,就像在这一个班轮中一样。

find . -d -type d -name "*$f" -execdir sh -c 'mv -- "$0" "$0%$1$2"' "" "$f" "$t" \;

这只会重命名匹配的文件夹,但它会在所有子文件夹中查找匹配项,使用 -maxdepth 2 限制遍历深度或添加 -mindepth 如果使用不同的起点进行校准。

开心!

【讨论】:

以上是关于在 Bash 中使用单行重命名目录及其子目录的主要内容,如果未能解决你的问题,请参考以下文章

在 bash 脚本中获取祖父目录 - 为路径中的目录重命名文件

ubuntu /bash 下递归重命名文件和目录

根据 Bash 中的模式更改目录名称

查找文件,原地重命名 unix bash

Python 或 Unix/bash。重命名目录中每个文件的第 11 列

根据 Bash 中的模式重命名目录