【问题标题】:How to rename large number of files如何重命名大量文件
【发布时间】:2020-11-20 22:58:22
【问题描述】:

我有一个目录,里面有这样的文件

a.JPG
b.JPG
c.JPG

我想做这样的事情

git mv a.JPG a.jpg

我尝试使用 xargs 和其他工具,但似乎没有任何效果。

【问题讨论】:

  • 您在下面的评论中提到您使用 OS X。这意味着您(可能)使用的是保留大小写不敏感的文件系统。因此重命名中的问题:您的文件系统将目标名称视为与目标名称相同,并且由于 Git 通过文件系统工作,因此它的外壳闪烁器也打开了。下面 wbyoung 的方法将通过将文件传递给可识别的不同中间名来解决问题。
  • 注意:git 2.0.1 解决了这个问题:stackoverflow.com/a/24979063/6309

标签: git command-line


【解决方案1】:

解决方案的核心将是使用自动批量重命名的工具/方法。您可以将 mvgit add 结合使用,也可以仅使用 git mv。在任何一种情况下,如果您使用不区分大小写的文件系统,您可能需要采取额外的步骤。所以在我们处理批量重命名之前,讨论一下如何处理大小写可能会很有用。

区分大小写

某些系统(或系统+文件系统组合——例如 Mac OS X 上 HFS+ 文件系统的默认变体*)保留大小写,但不区分大小写。在这样的系统上,在进行仅涉及更改名称大小写的重命名时可能需要小心。通常的解决方法是使用一个不仅仅是大小写不同的临时名称作为两个单独大小写不同的名称之间的“桥梁”(例如mv foo.JPG tmp && mv tmp foo.jpg)。

* 可以在 Mac OS X 上使用区分大小写的文件系统(包括大小写 HFS+ 的敏感变体)。

从这里开始,我将假设一个不区分大小写的文件系统。

Mac OS X 上的 mv 命令可以在一个步骤中处理仅更改大小写的重命名。它会给出“覆盖?”提示是否使用-i 选项运行,如果给定-n 选项,它将跳过重命名。它只有通过类 Unix 系统的许多部分的“足以吊死你自己”的默认操作才能成功。

git mv 命令对这种情况有点偏执。除非给出-f/--force 选项,否则它会拒绝操作(“destination exists”错误)。

# this will succeed, though it may fail/prompt if mv is aliased to use -n/-i
mv foo.JPG foo.jpg

# this will succeed
mv -f bar.JPG bar.jpg

# this will succeed but give a warning
git mv -f quux.JPG quux.jpg

批量重命名选项

Perl 重命名

所需的操作很简单,只需编写一些 shell 脚本即可,但如果您需要做很多事情,您可以使用 Perl rename 实用程序(Jordan Lewis 提到的那个)更复杂。你可以试试rename from Debian's perl package,或者如果你觉得可以使用CPAN,你可以安装File::Rename,它包括rename程序。

kshbashzshdash

下面使用的-ef 不兼容 POSIX。同样,虽然在 POSIX 中指定了 -e,但它不是纯 Bourne 兼容的。不过,它们都得到了广泛的支持。

for f in *.JPG; do
    ff="${f%.JPG}.jpg"
    test -e "$f" || continue        # possible when not using nullglob
    test "$f" != "$ff" || continue  # possible when using nocaseglob
    if test -e "$ff" &&
       ! test "$f" -ef "$ff"; then  # possible on a case sensitive filesystem
        echo "skipping <$f>: destination <$ff> exists and is distinct" 1>&2
        continue
    fi

    # "mv" with "git rm" and "git add"
    mv -f "$f" "$ff"     &&
    git rm --cached "$f" &&
    git add "$ff"
done

最后一部分(mvgit rmgit add)可以只替换为git mv

    # "git mv"
    git mv -f "$f" "$ff"

如果您非常担心在不区分大小写的系统上重命名可能会失败,那么您可以使用临时名称:

    # temp-based "mv" with "git rm" and "git add"
    t="$ff.tmp"; while test -e "$t"; do t="$t.tmp"; done
    mv -n "$f" "$t"      &&
    mv -n "$t" "$ff"     &&
    git rm --cached "$f" &&
    git add "$ff"

或者用 git mv:

    # temp-based "git mv"
    t="$ff.tmp"; while test -e "$t"; do t="$t.tmp"; done
    git mv "$f" "$t"  &&
    git mv "$t" "$ff"

zsh/zmv

zmvgit mv 都需要-f

zsh -c 'autoload zmv && $0 $@' zmv -fp git -o 'mv -f' '(*).JPG' '$1 x.jpg'

现在您已将它们全部重命名并在 Git 的索引中更新,您可以提交它们。

但是其他使用区分大小写的文件系统的 Git 用户是否能够检出它们?

git checkout 重命名后

如果您的历史记录中还有其他用户,他们可能仍然拥有JPG 文件,并且当他们最终使用jpg 文件检出您的提交(其后代)时。他们会发生什么?

无论发生什么,都不需要“rename to temp, commit, rename to final, commit”。 git checkout 在提交之间移动时不会按顺序应用提交。它通过将 HEAD 中的索引和工作树“合并”到新提交来真正起作用。这实际上意味着它直接“跳转”到新提交,同时拖动在 HEAD 和索引/工作树之间发现的非冲突更改。

在内部,Git 将重命名视为删除和添加。我没有找到任何描述 git checkout 关于删除和添加顺序的行为的文档,所以我查看了源代码。 git checkout 在任何更新/添加之前处理所有删除 (cmd_checkout -> switch_branches -> merge_working_tree (-> reset_tree) -> unpack_trees -> check_updates)。

您可以在重命名提交后立即进行测试:

git checkout HEAD~ # note: detached HEAD
# verify that the original names are back in place
git checkout -     # back to your branch
# verify that the new names are in place again

文件上的 git blame 似乎表明了一个可能的提交:Make unpack-tree update removed files before any updated files,它最初是在 Git 1.5.6-rc0 (2008-06-18) 中发布的。因此,尽管没有记录(?),但这种行为是专门为支持不区分大小写的文件系统而实现的。

谢谢,莱纳斯!

【讨论】:

    【解决方案2】:

    您是否可以更改文件的大小写取决于您的文件系统。即使它适用于您的文件系统,您也可能会导致其他人更新问题。你最好重命名它们,提交它们,然后重命名它们。使用以下 bash 脚本将所有内容更改为 *.tmp:

    for i in *.JPG; do mv $i ${i%.JPG}.tmp; done
    

    然后将它们全部移动到 git 中。您可以使用类似的命令,但我建议您查看guess-renames,这将有助于移动。

    然后通过类似的过程将它们全部重命名为 *.jpg。

    【讨论】:

    • 我想我会推荐 *.JPG 而不是 `ls`
    • 在提交之间移动时,Git 不会重播每个中间变更集。移动到新的提交会更新在当前 HEAD/索引和目标提交之间发生更改的文件。在多个阶段提交重命名不会帮助那些拥有无法直接处理mv foo.JPG foo.jpg 的机器的用户(除非他们在移动到“另一侧”的提交时手动“访问”“.tmp”提交)改名)。 Git 不需要 guess-renamesgit add -u &amp;&amp; git add &lt;new-names&gt; 将拾取没有 git mv 的“重命名”(或者,只需使用 git mv 而不是 mv)。
    【解决方案3】:

    使用标准 Linux rename(1) utility. 重命名文件后,使用 git 添加它们。

    【讨论】:

    • use "rename 's/JPG/jpg/' *.JPG" GIT 是内容寻址的,以便自动检测重命名文件(只要内容保持不变)。
    • 我用的是mac所以我没有重命名。
    • 如果您希望 git 将提交存储为重命名,而不是文件销毁和文件创建,则需要使用 git add -A .。 -A 表示检查删除(如果被跟踪),而不仅仅是文件创建。
    • 这将导致丢失相关文件的 git 日志历史记录。最好使用 git-mv。
    【解决方案4】:

    将文件扩展名从 .JPG 重命名为小写。这使用了git mv,因此 git 历史记录被保留。

    find . -name "*.JPG" | xargs -L1 bash -c 'git mv $0 ${0/JPG/jpg}'
    

    【讨论】:

    • 如果您在答案中添加一些解释会更好
    【解决方案5】:

    也许将它们重命名为 *.somethingelse,然后重命名回 *.jpg

    【讨论】:

      猜你喜欢
      • 2015-09-28
      • 2014-08-29
      • 2020-06-19
      • 2011-01-04
      • 2010-10-17
      • 1970-01-01
      • 2013-10-06
      相关资源
      最近更新 更多