【问题标题】:Batch renaming files with Bash使用 Bash 批量重命名文件
【发布时间】:2010-10-10 19:16:48
【问题描述】:

Bash 如何重命名一系列包以删除它们的版本号?我一直在玩弄expr 和%%,但无济于事。

例子:

Xft2-2.1.13.pkg 变为 Xft2.pkg

jasper-1.900.1.pkg 变为 jasper.pkg

xorg-libXrandr-1.2.3.pkg 变为 xorg-libXrandr.pkg

【问题讨论】:

  • 我打算定期使用它,作为一次写入多次使用的脚本。我将使用的任何系统都会有 bash,所以我不害怕非常方便的 bashism。

标签: bash shell file-rename


【解决方案1】:

我发现重命名是一种更直接的工具,可用于此类事情。我在 OSX 的 Homebrew 上找到了它

对于你的例子,我会这样做:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

“s”表示替代。形式是 s/searchPattern/replacement/files_to_apply。您需要为此使用正则表达式,这需要一点学习,但值得付出努力。

【讨论】:

  • 我同意。对于偶尔的事情,使用for 和sed 等很好,但如果你发现自己经常这样做,使用rename 会更简单快捷。
  • 你是在逃课吗?
  • 问题是这个不便携;不仅不是所有平台都有rename 命令;此外,有些将有一个不同 rename 命令,具有不同的功能、语法和/或选项。这看起来像是相当流行的 Perl rename;但答案应该真正澄清这一点。
  • 在 ubuntu/debian sudo apt-get install rename RedHat 派生发行版上安装重命名:sudo dnf install prename OSX:brew install rename
【解决方案2】:

我们可以假设sed 在任何 *nix 上都可用,但我们不能确定 它将支持sed -n 生成 mv 命令。 (注意:只有 GNU sed 这样做。)

即便如此,bash 内置函数和 sed,我们可以快速启动一个 shell 函数来执行此操作。

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

用法

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

创建目标文件夹:

由于mv 不会自动创建我们无法使用的目标文件夹 我们的初始版本sedrename。

这是一个相当小的变化,所以最好包含这个功能:

我们需要一个实用函数,abspath(或绝对路径),因为 bash 没有这个内置。

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

一旦我们有了它,我们就可以生成目标文件夹 包含新文件夹结构的 sed/rename 模式。

这将确保我们知道目标文件夹的名称。什么时候我们 rename 我们需要在目标文件名上使用它。

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

这是完整的文件夹感知脚本...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

当然,当我们没有特定的目标文件夹时它仍然有效 也是。

如果我们想将所有歌曲放入一个文件夹中,./Beethoven/ 我们可以这样做:

用法

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

奖金回合...

使用此脚本将文件从文件夹移动到单个文件夹中:

假设我们想收集所有匹配的文件,并放置它们 在当前文件夹中,我们可以这样做:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

关于 sed 正则表达式模式的注意事项

常规 sed 模式规则适用于此脚本,这些模式不适用 PCRE(Perl 兼容的正则表达式)。你可以 sed 扩展正则表达式语法,使用sed -r 或sed -E 取决于您的平台。

请参阅符合 POSIX 的 man re_format 以获取完整描述 sed 基本和扩展的正则表达式模式。

【讨论】:

    【解决方案3】:

    这是一个与 currently accepted answer 几乎等效的 POSIX。这将 Bash-only ${variable/substring/replacement} 参数扩展换成了任何 Bourne 兼容 shell 中可用的参数扩展。

    for i in ./*.pkg; do
        mv "$i" "${i%-[0-9.]*.pkg}.pkg"
    done
    

    参数扩展${variable%pattern} 生成variable 的值,删除了与pattern 匹配的任何后缀。 (还有${variable#pattern}可以去掉前缀。)

    我将子模式-[0-9.]* 保留在已接受的答案中,尽管它可能具有误导性。它不是正则表达式,而是 glob 模式;所以它并不意味着“一个破折号后跟零个或多个数字或点”。相反,它的意思是“一个破折号,后跟一个数字或一个点,然后是任何东西”。 “任何东西”将是最短的匹配,而不是最长的匹配。 (Bash 提供## 和%% 用于修剪可能的最长前缀或后缀,而不是最短的。)

    【讨论】:

      【解决方案4】:

      你可以使用 bash 的参数扩展功能

      for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done
      

      带空格的文件名需要引号。

      【讨论】:

      • 我也喜欢它,但它使用了 bash 特有的功能……我通常不使用它们来支持“标准”sh。
      • 对于一次性的单行交互的东西,我总是使用它而不是 sed-fu。如果它是一个脚本,是的,避免 bashisms。
      • 为避免重新运行错误,您可以在“.pkg”部分使用相同的模式,即“for i in *-[0-9.]。 pkg ; 做 mv $i ${i/-[0-9.]*.pkg/.pkg} ; 完成”。但是这些错误足够无害(移动到同一个文件)。
      • 不是 bash 解决方案,但如果您安装了 perl,它提供了方便的 rename 命令进行批量重命名,只需执行 rename "s/-[0-9.]*//" *.pkg
      • 什么是技术术语/手册页,我可以在{i/-[0-9.]*.pkg/.pkg} 中查找替换业务的详细信息?
      【解决方案5】:

      如果所有文件都在同一目录中,则顺序

      ls | 
      sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
      sh
      

      会做你的工作。 sed 命令将创建一系列 mv 命令,然后您可以通过管道将其导入 shell。最好先运行不带尾随 | sh 的管道,以验证该命令是否符合您的要求。

      要通过多个目录进行递归,请使用类似

      find . -type f |
      sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
      sh
      

      请注意,在 sed 中,正则表达式分组序列是括号前面加反斜杠,\( 和 \),而不是单括号 ( 和 )。

      【讨论】:

      • 原谅我的天真,但是用反斜杠转义括号不会使它们被视为文字字符吗?
      • 在 sed 中,正则表达式的分组顺序是 (,而不是单个括号。
      • 对我不起作用,很高兴得到您的帮助.... FROM 文件的后缀附加到 TO 文件: $find 。 -类型 d | sed -n 's/(.*)\/(.*)anysoftkeyboard(.*)/mv "\1\/\2anysoftkeyboard\3" "\1\/\2effectedkeyboard\3"/p'|sh >> >>>>>输出>>>> mv:将 ./base/src/main/java/com/anysoftkeyboard/base/dictionaries 重命名为 ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : 没有这样的文件或目录
      • 您似乎缺少括号中的反斜杠。
      • Don't use ls in scripts. 第一个变种可以使用printf '%s\n' * | sed ... 实现,并且您也可以通过一些创意摆脱sed。 Bash 的 printf 提供了 '%q' 格式代码,如果您的文件名包含文字 shell 元字符,它可能会派上用场。
      【解决方案6】:

      我在同一个文件夹中有多个 *.txt 文件要重命名为 .sql。 以下对我有用:

      for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
      

      【讨论】:

      • 您可以通过将其抽象为 OP 的实际用例来改进您的答案。
      • 这有多个问题以及明显的语法错误。请参阅shellcheck.net 了解一下。
      【解决方案7】:

      感谢您的回答。我也遇到了一些问题。将 .nzb.queued 文件移动到 .nzb 文件。它的文件名中有空格和其他杂物,这解决了我的问题:

      find . -type f -name "*.nzb.queued" |
      sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
      sh
      

      它基于 Diomidis Spinellis 的回答。

      正则表达式为整个文件名创建一组,为 .nzb.queued 之前的部分创建一组,然后创建一个 shell 移动命令。带引号的字符串。这也避免了在 shell 脚本中创建循环,因为这已经由 sed 完成。

      【讨论】:

      • 应该注意这仅适用于 GNU sed。在 BSD 上,sed 不会以相同的方式解释 -n 选项。
      【解决方案8】:

      最好使用sed,例如:

      find . -type f -name "*.pkg" |
       sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
       while read nameA nameB; do
          mv $nameA $nameB;
       done
      

      计算正则表达式作为练习(处理包含空格的文件名)

      【讨论】:

      • 感谢:名称中没有使用空格。
      【解决方案9】:

      我会这样做:

      for file in *.pkg ; do
          mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
      done
      

      假设您的所有文件都在当前目录中。如果没有,请尝试按照 Javier 的建议使用 find。

      编辑:此外,此版本不使用任何特定于 bash 的功能,如上述其他版本,这使您具有更高的可移植性。

      【讨论】:

      • 它们都在同一个目录下。你觉得rq的回答怎么样?
      • 我不喜欢使用特定于 bash 的功能,而是使用更标准的 sh。
      • 我认为这是为了快速重命名,而不是为了编写下一代跨平台、可移植的企业应用程序 ;-)
      • 哈哈,够公平的......只是出于像我这样的便携意识的人:)
      • 这个不考虑第三个例子:xorg-libXrandr(名称中使用连字符)。
      【解决方案10】:

      这似乎工作假设

      • 一切都以 $pkg 结尾
      • 您的版本号始终以“-”开头

      剥离 .pkg,然后剥离 -..

      for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
      

      【讨论】:

      • 有些软件包的名称中有连字符(参见第三个示例)。这会影响您的代码吗?
      • 您可以使用-e 运行多个 sed 表达式。例如。 sed -e 's/\.pkg//g' -e 's/-.*//g'.
      • Don't parse ls output 和 quote your variables. 另见 shellcheck.net 以诊断 shell 脚本中的常见问题和反模式。
      猜你喜欢
      • 2012-09-02
      • 2021-04-04
      • 1970-01-01
      • 1970-01-01
      • 2012-12-24
      • 2017-01-01
      • 2015-12-12
      • 2015-05-05
      • 2011-01-23
      相关资源
      最近更新 更多