2009-03-02 380 views
73

Bash如何重命名一系列软件包以删除其版本号?我一直在玩expr%%,无济于事。使用Bash批量重命名文件

例子:

Xft2-2.1.13.pkg成为Xft2.pkg

jasper-1.900.1.pkg成为jasper.pkg

xorg-libXrandr-1.2.3.pkg成为xorg-libXrandr.pkg

+1

我打算经常使用它,作为一次写入使用的批量脚本。 我将使用的任何系统都会有打击,所以我不怕那些非常方便的bashisms。 – 2009-03-02 15:45:40

+0

更普遍的另请参阅https://stackoverflow.com/questions/28725333/looping-over-pairs-of-values-in-bash – tripleee 2018-03-04 19:19:14

回答

138

你可以使用bash的参数扩展功能都需要有空格的文件名

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

行情。

+1

我喜欢这个迄今为止最好的! – 2009-03-02 15:36:46

+1

我也喜欢它,但它使用bash特有的功能......我通常不会使用它们来支持“标准”sh。 – 2009-03-02 15:39:05

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 

搞清楚了正则表达式作为一个练习(如处理那些包含空格的文件名)

+0

谢谢:名称中不使用空格。 – 2009-03-02 15:34:47

1

这似乎是工作假设

  • 一切以$ PKG
  • 你的版本结束“ - ”

条断的.pkg,然后脱掉 - ..

#的总是以一个开始
for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done 
+0

有些软件包的名称中有一个连字符(请参阅第三个示例)。这会影响你的代码吗? – 2009-03-02 15:35:54

+1

您可以使用`-e`运行多个sed表达式。例如。 `sed -e's/\。pkg // g'-e's /-.*// g'`。 – nojak 2017-08-31 23:05:42

+0

[不要解析`ls`输出](https://mywiki.wooledge.org/ParsingLs)和[引用您的变量]。(/ q/10067266)另请参阅http://shellcheck.net/用于诊断常见问题和shell脚本中的反模式。 – tripleee 2018-03-04 19:30:14

7

我会做这样的事情:

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

认为所有的文件都在当前目录中。如果不是,请按照Javier的建议使用find。

编辑此外,此版本不使用任何bash特有的功能,如上面的其他功能,这将导致您更多的便携性。

23

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

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

会做你的工作。命令sed命令将创建一系列命令,然后您可以将它们传送到shell中。最好首先运行流水线,不要拖尾| sh以验证命令是否符合您的要求。

递归通过多个目录中使用类似

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

注意,在sed的正则表达式分组序列是一个反斜杠,\(\),而不是单一的支架()前支架。

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完成了。

1

我有多个*.txt文件被重命名为.sql在同一个文件夹中。下面 工作对我来说:

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

这里是一个POSIX近相当于currently accepted answer的。这交换了仅限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.]*从接受的答案,虽然它也许是误导。这不是一个正则表达式,而是一个glob模式;所以它并不意味着“破折号后跟零个或多个数字或点”。相反,它的意思是“短划线,后面跟着一个数字或一个点,然后是任何东西”。 “任何事物”将是最短的匹配,而不是最长的。 (修剪最长可能前缀或后缀,而不是最短的Bash提供##%%

0

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

即使如此,bash内置和sed,我们可以快速掀起一个shell函数来做到这一点。

sedrename() { 
    if [ $# -gt 1 ]; then 
    sed_pattern=$1 
    shift 
    for file in $(ls [email protected]); 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(或绝对路径),因为庆典 没有这个版本英寸

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

一旦我们有了我们可以生成用于sed的一个 目标文件夹(或多个)/重命名图案,其中包括新的文件夹结构。

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

# 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 [email protected]); 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 

奖金圆...

使用这个脚本从移动文件文件夹放到一个文件夹中:

假设我们想收集所有匹配的文件,并将它们放在当前的f年纪大了,我们可以做到这一点:在SED正则表达式模式

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模式规则适用于该脚本,这些模式都没有 PCRE(Perl兼容正则表达式)。根据您的平台,您可以使用sed -rsed -E 扩展正则表达式语法sed 。

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