使用 Bash 批量重命名文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/602706/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:59:24  来源:igfitidea点击:

Batch renaming files with Bash

bashshellfile-rename

提问by Jeremy L

How can Bash rename a series of packages to remove their version numbers? I've been toying around with both exprand %%, to no avail.

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

Examples:

例子:

Xft2-2.1.13.pkgbecomes Xft2.pkg

Xft2-2.1.13.pkg变成 Xft2.pkg

jasper-1.900.1.pkgbecomes jasper.pkg

jasper-1.900.1.pkg变成 jasper.pkg

xorg-libXrandr-1.2.3.pkgbecomes xorg-libXrandr.pkg

xorg-libXrandr-1.2.3.pkg变成 xorg-libXrandr.pkg

回答by richq

You could use bash's parameter expansion feature

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

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

Quotes are needed for filenames with spaces.

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

回答by Diomidis Spinellis

If all files are in the same directory the sequence

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

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

will do your job. The sedcommand will create a sequence of mvcommands, which you can then pipe into the shell. It's best to first run the pipeline without the trailing | shso as to verify that the command does what you want.

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

To recurse through multiple directories use something like

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

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

Note that in sedthe regular expression grouping sequence is brackets preceded by a backslash, \(and \), rather than single brackets (and ).

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

回答by Diego Sevilla

I'll do something like this:

我会做这样的事情:

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

supposed all your file are in the current directory. If not, try to use find as advised above by Javier.

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

EDIT: Also, this version don't use any bash-specific features, as others above, which leads you to more portability.

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

回答by tripleee

Here is a POSIX near-equivalent of the currently accepted answer. This trades the Bash-only ${variable/substring/replacement}parameter expansion for one which is available in any Bourne-compatible shell.

这是当前接受的答案的 POSIX 近等价。这将 Bash-only${variable/substring/replacement}参数扩展交换为在任何 Bourne 兼容 shell 中可用的参数扩展。

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

The parameter expansion ${variable%pattern}produces the value of variablewith any suffix which matches patternremoved. (There is also ${variable#pattern}to remove a prefix.)

参数扩展${variable%pattern}产生的值variablepattern删除匹配的任何后缀。(还有${variable#pattern}删除前缀。)

I kept the subpattern -[0-9.]*from the accepted answer although it is perhaps misleading. It's not a regular expression, but a glob pattern; so it doesn't mean "a dash followed by zero or more numbers or dots". Instead, it means "a dash, followed by a number or a dot, followed by anything". The "anything" will be the shortest possible match, not the longest. (Bash offers ##and %%for trimming the longest possible prefix or suffix, rather than the shortest.)

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

回答by ocodo

We can assume sedis available on any *nix, but we can't be sure it'll support sed -nto generate mv commands. (NOTE:Only GNU seddoes this.)

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

Even so, bash builtins and sed, we can quickly whip up a shell function to do this.

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

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: 
sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)||' *.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
sed_pattern files..." fi }

Usage

用法

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

Creating target folders:

创建目标文件夹:

Since mvdoesn't automatically create target folders we can't using our initial version of sedrename.

由于mv不会自动创建目标文件夹,因此我们无法使用sedrename.

It's a fairly small change, so it'd be nice to include that feature:

这是一个相当小的变化,因此包含该功能会很好:

We'll need a utility function, abspath(or absolute path) since bash doesn't have this build in.

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

# 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"

Once we have that we can generate the target folder(s) for a sed/rename pattern which includes new folder structure.

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

This will ensure we know the names of our target folders. When we rename we'll need to use it on the target file name.

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

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

Here's the full folder aware script...

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

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

Of course, it still works when we don't have specific target folders too.

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

If we wanted to put all the songs into a folder, ./Beethoven/we can do this:

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

Usage

用法

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

Bonus round...

奖金回合...

Using this script to move files from folders into a single folder:

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

Assuming we wanted to gather up all the files matched, and place them in the current folder, we can do it:

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

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

Note on sed regex patterns

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

Regular sed pattern rules apply in this script, these patterns aren't PCRE (Perl Compatible Regular Expressions). You could have sed extended regular expression syntax, using either sed -ror sed -Edepending on your platform.

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

See the POSIX compliant man re_formatfor a complete description of sed basic and extended regexp patterns.

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

回答by Simon Katan

I find that rename is a much more straightforward tool to use for this sort of thing. I found it on Homebrew for OSX

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

For your example I would do:

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

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

The 's' means substitute. The form is s/searchPattern/replacement/ files_to_apply. You need to use regex for this which takes a little study but it's well worth the effort.

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

回答by Javier

better use sedfor this, something like:

更好地sed用于此,例如:

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

figuring up the regular expression is left as an exercise (as is dealing with filenames that include spaces)

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

回答by Steve B.

This seems to work assuming that

这似乎工作假设

  • everything ends with $pkg
  • your version #'s always start with a "-"
  • 一切都以 $pkg 结尾
  • 您的版本号总是以“-”开头

strip off the .pkg, then strip off -..

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

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

回答by Kumar Mashalkar

I had multiple *.txtfiles to be renamed as .sqlin same folder. below worked for me:

我有多个*.txt文件要重命名为.sql同一个文件夹。以下对我来说有效:

##代码##

回答by Jerry Jacobs

Thank you for this answers. I also had some sort of problem. Moving .nzb.queued files to .nzb files. It had spaces and other cruft in the filenames and this solved my problem:

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

##代码##

It is based on the answer of Diomidis Spinellis.

它基于 Diomidis Spinellis 的回答。

The regex creates one group for the whole filename, and one group for the part before .nzb.queued and then creates a shell move command. With the strings quoted. This also avoids creating a loop in shell script because this is already done by sed.

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