用于批量重命名多个文件的 shell 语句

用于批量重命名多个文件的 shell 语句

我在寻找两个重命名单行管理我的音乐库。

第一的担忧文件名。我想使用尽可能简单的名称,即:%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.flacXX 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,您可以使用findperl来获得类似以下内容的内容:

  • XX - title.flacXX 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.flacXX 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 *,如果当前工作目录中没有非隐藏文件,则循环中仍然会有一个路径*作为文件名。zshzmv报告有关不匹配文件的错误。
  • 使用 oksh glob 时,您无法通过文件类型限制 glob 扩展(如 zsh 的(#q.)===-type f(#q/)=== -type d)。
  • 与上述方法相反,您只是重命名当前工作目录中的文件,因此您需要在每个目录中重复该过程。

相关内容