使用Bash批量重命名文件


101

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


1
我打算定期使用它作为一次写入多次使用脚本。我将要使用的任何系统都会受到重击,因此我不惧怕方便的重击。
杰里米·L,2009年

Answers:


190

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

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

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


1
我也喜欢它,但是它使用bash特有的功能...我通常不赞成使用“标准” sh。
迭戈塞维利亚,2009年

2
对于一次性的交互式交互式东西,我总是使用它代替sed-fu。是的,如果是脚本,请避免奉公道教。
richq

我在代码中发现了一个问题:如果文件已经被重命名并且再次运行它会发生什么?我收到尝试移入其自身子目录的警告。
Jeremy L

1
为避免重新运行错误,可以在“ .pkg”部分中使用相同的模式,即“对于*-[0-9。]。 pkg中的i;对mv $ i $ {i /-[0-9 。] *。pkg / .pkg};完成”。但是错误足够无害(移至同一文件)。
richq

3
不是bash解决方案,但是,如果您安装了perl,它会提供方便的重命名命令以进行批量重命名,只需执行rename "s/-[0-9.]*//" *.pkg
Rufus

33

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

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

会做你的工作。的sed的命令将创建的序列MV命令,这些命令可以然后管进入外壳。最好先运行管道,不要拖尾| sh,以验证该命令是否满足您的要求。

要遍历多个目录,请使用类似

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

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


原谅我的天真,但是不会用反斜杠转义括号使它们被视为原义字符吗?
devios1 2014年

2
在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 :没有这样的文件或目录
Vitali Pom,2018年

您似乎在方括号中缺少反斜杠。
Diomidis Spinellis

1
不要ls在脚本中使用。第一个品种可以通过printf '%s\n' * | sed ...创造力来实现,并且您也可以摆脱一些创造力sed。Bash printf提供了一种'%q'格式代码,如果您的文件名包含文字壳元字符,它可能会派上用场。
Tripleee '18

10

我会做这样的事情:

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

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

编辑:而且,此版本不使用任何特定于bash的功能,就像上面的其他功能一样,这使您具有更大的可移植性。


它们都在同一目录中。您如何看待rq的答案?
杰里米·L,2009年

我不喜欢不使用bash特有的功能,而是使用更标准的sh。
迭戈塞维利亚,2009年

1
我认为这是为了快速重命名,而不是为了编写下一代跨平台的可移植企业应用程序;-)
richq 2009年

1
哈哈,足够公平……刚从一个像我这样具有便携性的人中走出来:)
Diego Sevilla 2009年

这个没有说明第三个示例:xorg-libXrandr(名称中使用连字符)。
Jeremy L

5

这是POSIX几乎等同于当前接受的答案。这将仅Bash ${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.]*尽管可能会引起误解,但我还是拒绝了该子模式。它不是一个正则表达式,而是一个全局模式。因此,这并不意味着“短划线后跟零个或多个数字或点”。相反,它的意思是“一个破折号,后跟一个数字或一个点,后跟任何东西”。“一切”将是最短的匹配,而不是最长的匹配。(Bash提供##%%用于修剪可能的最长前缀或后缀,而不是最短的前缀。)


5

我们可以假定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模式生成目标文件夹,其中包括新的文件夹结构。

这将确保我们知道目标文件夹的名称。重命名时,需要在目标文件名上使用它。

# 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 根据您的平台使用。

有关man re_formatsed基本和扩展正则表达式模式的完整说明,请参阅POSIX兼容。


4

我发现重命名是用于这种事情的更直接的工具。我在Homebrew for OSX上找到了它

以您的示例为例:

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

“ s”表示替代。格式为s / searchPattern / replacement / files_to_apply。您需要为此使用正则表达式,这需要进行一些研究,但是值得付出努力。


我同意。对于一个偶然的事情,使用forsed等等是好的,但它更简单,快捷,只需使用rename如果你发现自己经常这样做。
argentum2f

那是您逃避时期吗?
Big Money

问题是这不是便携式的。并非所有平台都具有rename命令;另外,某些命令将具有不同的 rename命令,这些命令具有不同的功能,语法和/或选项。看起来很rename流行的Perl 。但是答案确实可以澄清这一点。
点钟

3

更好地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

找出正则表达式作为练习(就像处理包含空格的文件名一样)


谢谢:名称中未使用空格。
杰里米·L,2009年

1

假设

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

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

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

有些软件包的名称中带有连字符(请参见第三个示例)。这会影响您的代码吗?
杰里米·L,2009年

1
您可以使用来运行多个sed表达式-e。例如sed -e 's/\.pkg//g' -e 's/-.*//g'
tacotuesday

不要解析ls输出引用变量。另请参阅shellcheck.net,以诊断shell脚本中的常见问题和反模式。
三胞胎

1

我有多个*.txt文件要重命名为.sql同一文件夹中。下面为我​​工作:

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

2
您可以通过将其抽象为OP的实际用例来改善答案。
dakab,2015年

这有多个问题以及明显的语法错误。请参阅shellcheck.net作为开始。
18

0

感谢您的回答。我也有一些问题。将.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相同的方式解释该选项。
ocodo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.