115

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

4

10 回答 10

214

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

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

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

于 2009-03-02T15:33:46.907 回答
38

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

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中,正则表达式分组序列是括号前面有反斜杠 \(\),而不是单括号()

于 2009-03-02T15:54:22.097 回答
11

我会做这样的事情:

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

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

编辑:此外,此版本不使用任何特定于 bash 的功能,就像上面的其他功能一样,这会导致您获得更多的可移植性。

于 2009-03-02T15:35:54.687 回答
7

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

即便如此,使用 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 -r或使用 sed 扩展的正则表达式语法。sed -E

有关 sed 基本和扩展正则表达式模式的完整描述,请参阅 POSIX 兼容man re_format

于 2018-03-11T01:50:21.803 回答
6

这是与当前接受的答案几乎等效的 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.]*已接受答案的子模式,尽管它可能具有误导性。它不是正则表达式,而是全局模式;所以它并不意味着“一个破折号后跟零个或多个数字或点”。相反,它的意思是“一个破折号,后跟一个数字或一个点,然后是任何东西”。“任何东西”将是最短的匹配,而不是最长的匹配。(Bash 提供##%%用于修剪可能的最长前缀或后缀,而不是最短的。)

于 2018-03-04T19:41:55.443 回答
5

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

对于您的示例,我会这样做:

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

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

于 2018-08-22T09:05:25.937 回答
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

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

于 2009-03-02T15:30:15.150 回答
1

假设这似乎有效

  • 一切都以 $pkg 结尾
  • 你的版本号总是以“-”开头

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

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
于 2009-03-02T15:33:35.907 回答
1

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

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
于 2015-11-05T17:22:40.543 回答
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 完成。

于 2014-10-30T08:53:22.487 回答