![用于批量重命名多个文件的 shell 语句](https://linux22.com/image/226843/%E7%94%A8%E4%BA%8E%E6%89%B9%E9%87%8F%E9%87%8D%E5%91%BD%E5%90%8D%E5%A4%9A%E4%B8%AA%E6%96%87%E4%BB%B6%E7%9A%84%20shell%20%E8%AF%AD%E5%8F%A5.png)
我在寻找两个重命名单行管理我的音乐库。
第一的担忧文件名。我想使用尽可能简单的名称,即:%track %title.ext
例如01 Song name.mp3
13 Song title.flac
等。我当前使用连字符作为分隔符(曲目编号和歌曲标题之间)的文件,我认为这是多余的。
第二担忧文件夹名称,我需要切换一些图案
从GENRE - Styles - ARTIST - Album (Year)
到GENRE - Styles - ARTIST - Year Album
(切换年份和专辑部分也省略括号)
(我运行 openbsd克什88.)
这是我为之编造的最接近的一句台词文件名案例:
for f in * ; do mv "$f" "$(print "$f" | sed "s/ - / /")" ; done
但是还没有起作用。我想这与命令的第二个参数(目标)的正确封装有关mv
(编辑:添加了引号$( ... )
并且它有效,这是我没想到的)
$pwd
/home/media/E - Chanson Française/CHANSON - Chanson Française - CHARLES TRENET - Le meilleur de Charles Trenet
$l -1
01 - Boum!.flac
02 - La route enchantée.flac
03 - Ah! Dis, Ah! Dis, Ah! Bonjour!.flac
04 - La vie qui va.flac
05 - Quand j'etais p'tit.flac
06 - Ménilmontant.flac
07 - Tout me sourit.flac
08 - Le oiseaux de Paris.flac
...
$for f in * ; do mv "$f" "$(print "$f" | sed 's/ - / /')" ; done
$l -1
01 Boum!.flac
02 La route enchantée.flac
03 Ah! Dis, Ah! Dis, Ah! Bonjour!.flac
04 La vie qui va.flac
05 Quand j'etais p'tit.flac
06 Ménilmontant.flac
07 Tout me sourit.flac
08 Le oiseaux de Paris.flac
...
我认为嵌套双引号会产生错误,但它有效。这样,第一个案例就解决了。
这文件夹名称案件让我更加回避。以下是我想更改的名称示例:
$ ls -1
...
ELECTRO - Cyberpunk - CARPENTER BRUT - Blood Machines (2020)
ELECTRO - Cyberpunk - CARPENTER BRUT - Leather Teeth (2018)
ELECTRO - Cyberpunk - CARPENTER BRUT - Leather Terror (2022)
ELECTRO - Cyberpunk - CARPENTER BRUT - Trilogy (2015)
...
REGGAE - Roots - GROUNDATION - Each One Teach One (2001)
REGGAE - Roots - GROUNDATION - Hebron Gate (2002)
REGGAE - Roots - GROUNDATION - Upon the Bridge (2006)
REGGAE - Roots - GROUNDATION - Young Tree (1999)
...
结果应该是这样的:
$ ls -1
...
ELECTRO - Cyberpunk - CARPENTER BRUT - 2015 Trilogy
ELECTRO - Cyberpunk - CARPENTER BRUT - 2018 Leather Teeth
ELECTRO - Cyberpunk - CARPENTER BRUT - 2020 Blood Machines
ELECTRO - Cyberpunk - CARPENTER BRUT - 2022 Leather Terror
...
REGGAE - Roots - GROUNDATION - 1999 Young Tree
REGGAE - Roots - GROUNDATION - 2001 Each One Teach One
REGGAE - Roots - GROUNDATION - 2002 Hebron Gate
REGGAE - Roots - GROUNDATION - 2006 Upon the Bridge
...
我想我需要使用 awk 来切换字段:类似for f in *CARPENTER\ BRUT* ; do mv "$f" "$( print "$f" | awk ... )" ; done
- 用 awk 切换最后一个连字符后的年份,并可能用 tr 删除括号 - 但我在这一点上学习有困难。
答案1
如果可以安装 zsh:
XX - title.flac
到XX title.flac
常规文件。zsh <<\EOF autoload zmv zmv -n '(**/)(<-> )- (*.(#i)(ogg|mp3|flac))(#q.)' '$1$2$3' < /dev/tty EOF
GENRE - Styles - ARTIST - Album (Year)
到GENRE - Styles - ARTIST - Year Album
目录zsh <<\EOF autoload zmv zmv -n '(**/)(* - * - * - )(*) \((<->)\)(#q/)' '$1$2$4 $3' < /dev/tty EOF
如果满意,请删除-n
(试运行)。
如果您无法安装 zsh,您可以使用find
和perl
来获得类似以下内容的内容:
XX - title.flac
到XX title.flac
常规文件LC_ALL=C find . -depth -name '[0-9]*.*' -type f -print0 | perl -l -0ne ' ($dir, $base) = m{^(.*/)(.*)\z}s; if ($base =~ s/^(\d+ )- (.*\.(?i:ogg|mp3|flac))\z/$1$2/s) { print qq(rename($_, "$dir$base")) or warn "$_: $!"; }'
GENRE - Styles - ARTIST - Album (Year)
到GENRE - Styles - ARTIST - Year Album
目录LC_ALL=C find . -depth -name '*(*)' -type d -print0 | perl -l -0ne ' ($dir, $base) = m{^(.*/)(.*)\z}s; if ($base =~ s/( - .* - .* - )(.*) \((\d+)\)\z/$1$3 $2/is) { print qq(rename($_, "$dir$base")) or warn "$_: $!"; }'
并删除print qq(...)
周围的rename()
如果快乐。
但要注意的是:
- 它还将处理隐藏文件/目录(以及在隐藏目录中)
- 它没有任何保护措施
zmv
,因此它最终可能会覆盖现有文件。
使用 OpenBSD 的 ksh 和基本 POSIX 实用程序来完成此操作是可能的,但非常笨重:
可能:
XX - title.flac
到XX title.flac
常规文件。LC_ALL=C find . -depth -name '[0-9][0-9] - *' '(' \ -name '*.[Ff][Ll][Aa][Cc]' -o \ -name '*.[Oo][Gg][Gg]' -o \ -name '*.[Mm][Pp]3' ')' -type f -exec ksh -c ' for file do dir=${file%/*} base=${file##*/} print -r mv -i -- "$file" "$dir/${base%% *}${base#* -}" done' {} +
GENRE - Styles - ARTIST - Album (Year)
到GENRE - Styles - ARTIST - Year Album
目录LC_ALL=C find . -depth -name '* - * - * - * (*[0-9])' -type d -exec ksh -c ' for file do dir=${file%/*} base=${file##*/} if [[ $base = *" ("+([0-9])")" ]]; then album="${base##* - }" year=${album##*\(} year=${year%\)} base=${base%"$album"} album=${album%" ("*} print -r mv -i -- "$file" "$dir/$base$year $album" fi done' {} +
(如果高兴则删除print -r
)。
与该perl
方法相同的警告,尽管-i
传递的选项mv
有助于避免数据丢失(而不是mv
从搬去到搬进如果目标文件存在并且是目录类型)。
关于您的尝试:
sed
是一个文本实用程序,并且是基于行的,而文件路径不能保证包含文本,更不用说单行文本,因此最好避免处理文件名的文件路径。print
,就像默认情况下接受选项的许多实现一样echo
(其中一些可能会导致执行任意命令,尽管 OpenBSD 的 ksh 实现似乎并非如此)并执行一些反斜杠转义序列扩展。要逐字输出文本(后跟换行符,因此它以有效行结尾),您需要print -r - "$text"
或print -r -- "$text"
,特别是如果您不能保证$text
不会以 开头-
。- 通过将命令替换 (
$(...)
) 不加引号, split+glob 最终将应用于结果。是的,你可以引用它。如果您使用已弃用的命令替换形式,嵌套引用只会成为问题`...`
,但即使如此,它也是可能的,只是使用cmd1 "`other-cmd \" $var \"`"
. - 当心
$(...)
删除全部尾随换行符,因此通常最终会删除太多。 --
mv
之后也失踪了。mv
即使sed
没有修改文件路径,您也会调用。- 在OpenBSD的ksh中
for i in *
,如果当前工作目录中没有非隐藏文件,则循环中仍然会有一个路径*
作为文件名。zsh
会zmv
报告有关不匹配文件的错误。 - 使用 oksh glob 时,您无法通过文件类型限制 glob 扩展(如 zsh 的
(#q.)
===-type f
和(#q/)
===-type d
)。 - 与上述方法相反,您只是重命名当前工作目录中的文件,因此您需要在每个目录中重复该过程。