【问题标题】:Linux, rename files with bash 'no such file or directory' errorLinux,使用 bash 重命名文件“没有这样的文件或目录”错误
【发布时间】:2017-01-17 15:02:38
【问题描述】:

我正在尝试为当前目录和所有子目录中所有不是.sh 文件的文件添加前缀,其中bash 使用find

问题是我得到一个错误:

mv: cannot move './test.txt' to 'PRE_./test.txt': No such file or directory

我的单行是:

find -type f ! -name "*.sh" -exec sh -c 'mv "{}" PRE_"{}"' _ {} \;

我也试过 xargsrename

find -type f ! -name "*.sh" -print0 | xargs -0 rename 's/(.*)$/PRE_$1/'

但我遇到了同样的错误。 怎么了? 提前致谢!

【问题讨论】:

  • cannot move './test.txt' to 'PRE_./test.txt' 似乎是一个非常明确的信息——看看你想给它取的新名字,这是你想要的吗?您正在为整个路径添加前缀,而不仅仅是文件名。
  • 就在当前目录下,还是在当前目录下的所有子目录下?
  • 哦,忘了说:我也想重命名所有子目录中的文件。

标签: linux bash find pathname


【解决方案1】:

find 返回从. 一直到文件的路径,因此当您在当前目录中开始搜索时,每个find 结果都以./ 开头。结果,您的新文件名为PRE_./test.txt,即test.txt,位于名为PRE_. 的目录中。由于没有名为PRE_. 的目录,mv 无法将test.txt 移动到该目录中,因此会显示您看到的错误消息。

如果你只是重命名当前目录中的文件,而不是子目录,你可以使用 bash:

shopt -s extglob 
for f in !(*.sh) ; do [[ -d "$f" ]] || mv "$f" "PRE_$f" ; done

感谢 this answer 在 bash 中的 !(pattern) 构造,在启用 extglob 时可用。它返回所有匹配pattern的文件名。

对于所有子目录中的文件,也可以使用:

shopt -s extglob globstar
for f in **/!(*.sh) ; do [[ -d "$f" ]] || mv "$f" "${f%/*}/PRE_${f##*/}" ; done

感谢 this answer 提供 globstar。 %## 切开路径组件并将PRE_ 粘贴到文件名开头的正确位置。

编辑以上两个中的[[ -d "$f" ]] || 可防止循环重命名目录。

【讨论】:

    【解决方案2】:

    最好不要在sh -c 的参数中直接使用{},而是将其值作为参数传递。然后你可以像往常一样使用参数扩展操作来修改它的值。在这里,我们将参数拆分为其目录和文件名组件,然后重新组合它们,包括文件名前缀 PRE_ 以用作 mv 的参数。

    script='
    d=${1%/*}
    f=${1#$d}
    mv "$1" "$d/PRE_$f"
    '
    find -type f ! -name "*.sh" -exec sh -c "$script" _ {} \;
    

    (这与您最初的尝试略有不同;您将{} 作为参数传递,但没有使用它。相反,您将值直接“嵌入”到传递给sh 的字符串中。)

    您还可以使用-exec + 表单来最小化对sh 的调用次数,方法是传递多个文件名并在shell 中对其进行迭代。 (这开始与@cxw 建议的纯bash 解决方案趋同,但保持POSIX,以及bash 3.x,兼容性):

    script='
    for arg;
     d=${arg%/*}
     f=${arg#$d}
     mv "$1" "$d/PRE_$f"
    done
    '
    find -type f ! -name "*.sh" -exec sh -c "$script" _ {} +
    

    【讨论】:

    • 这不会尝试将./foo/bar/bat 移动到./PRE_foo/bar/bat 而不是./foo/bar/PRE_bat 吗?在我看来,$f 会得到 foo/bar/bat。 (当然,我可能会遗漏一些东西。)
    • 是的。我会解决的。
    【解决方案3】:

    如果您不想更改子目录中的文件,可以使用:

    find -type f ! -name "*.sh" -printf "%f\n" | xargs -r rename 's/(.*)$/PRE_$1/'
    

    问题是 ./ 在文件名前面。

    【讨论】:

      【解决方案4】:
      find -type f ! -name "*.sh" -exec rename -n 's|.*/\K|PRE_|' {} +
      
      • .*/ 匹配文件名中的最后一个 /
      • 通过使用\Klookbehind,我们可以避免在这种情况下需要捕获组

      一旦显示正确的重命名,从rename 中删除-n 选项

      【讨论】:

      • 谢谢,这正是我需要的
      猜你喜欢
      • 2016-06-25
      • 1970-01-01
      • 1970-01-01
      • 2021-04-29
      • 1970-01-01
      • 1970-01-01
      • 2020-07-27
      • 2018-09-14
      • 1970-01-01
      相关资源
      最近更新 更多