【问题标题】:Bash script; Renaming files in /subdirectoriesbash 脚本;重命名/子目录中的文件
【发布时间】:2021-11-27 05:56:45
【问题描述】:

我在老式 BBS 上托管了大量文件档案。 [Mystic] 软件不像具有长文件名或扩展字符的 Linux 那样宽容或强大。

文件名的长度应少于 80 个字符。

文件名只能包含字符 A-Z 和 1-9。没有“!@ # $ % ^ &”等 - 也没有带有波浪号或插入符号的字母。

这是一个集合目录的示例:

pi@bbs:/mnt/Beers4TB/opendirs/TDC19 $ ls -all
total 28
drwxrwxr-x  6 pi pi 4096 Sep 16 08:08 .
drwxrwxr-x 11 pi pi 4096 Oct  6 15:04 ..
drwxrwxr-x  2 pi pi 4096 Sep 13 20:13 ANSi
drwxrwxr-x  2 pi pi 4096 Oct  6 21:16 Drivers
drwxrwxr-x 10 pi pi 4096 Sep 16 08:12 Games
-rw-rw-r--  1 pi pi 1056 Sep 13 20:12 INTRO.TXT
drwxrwxr-x  2 pi pi 4096 Sep 16 08:08 ListsNotes

在 /subdirectories 中,它们可能会更深 2、3 或更深。

以下是一些文件当前命名的示例:

pi@bbs:/mnt/Beers4TB/opendirs/TDC19/Games/Applications $ ls M*
'Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip'
'Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip'
'Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].zip'
'Might and Magic III Character Editor (1991)(Blackbeard'\''s Ghost) [Utility].zip'
'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip'

我已经处理了一些有希望的事情......这个 echo/sed 命令删除了一些高字符:

echo "Might and Magic III Character Editor (1991)(Blackbeard'''s Ghost) [Utility].zip" | sed -r -e 's/\x27+//g' -e 's/[][")(]//g' -e 's/[ ]+//g'

(它重命名文件:) Might_and_Magic_III_Character_Editor_1991_Blackbeards_Ghost_Utility.zip

然后,我有一个命令可以重命名整个 / 子目录,但它不会删除任何字符:

for f in *.zip; do mv "${f}" "${f//[][\")( ]/_}"; done

这很好...但我必须摆脱高字符...而且,这种方法有时会在文件名中添加多个空格 - 这增加了最大 80 个文件名限制 - 并且没有内置安全保护...

我致力于添加对通过多个 / 子目录的支持,但我知道我的语法仍然是错误的……但是,您可以看看我在尝试做什么:

P=$(pwd); for D in $(find . -maxdepth 1 -type d); do cd $D; for f in *.zip; do mv "${f}" "${f//[][\")( ]/_}"; cd $P; done

所以,最后——我对任何 Linux 命令持开放态度: 删除任何不是 A-Z 或 1-9 的字符。 删除文件名中的任何多余空格。 确保文件名最多只有 80 个字符,只需删除 .zip(或 .anything)扩展名之前的最后一位。 从主 / 目录开始并重命名主目录中每个 / 子目录中的所有文件。

最后;我总是首先尝试将事情放在一起......其次我从同事那里得到帮助 - 我最后来到互联网......但我想了解如何自己编写这种确切的东西。如果您对在哪里学习有任何建议,那也会很受欢迎。这次我试图正确地发布这个问题,如果我没有把每条规则都正确,请原谅。

pAULIE42o . . . . . /s

【问题讨论】:

标签: linux bash file rename script


【解决方案1】:

使用while + read 循环、Process Substitutionfind 加上mv 重命名文件。


脚本。

#!/usr/bin/env bash

shopt -s extglob nullglob

while IFS= read -rd '' directory; do
  if [[ -e $directory && -x $directory ]] ; then
    (
      printf 'Entering directory %s\n' "$directory"
      cd "$directory" || exit
      files=(*.zip)
      (( ${#files[*]} )) || {
        printf 'There are no files ending in *.zip here!, moving on...\n'
        continue
      }
      for file_name_with_extension in *.zip; do
        extension=${file_name_with_extension##*.}
        file_name_without_extension=${file_name_with_extension%."$extension"}
        change_spaces_to_underscore="${file_name_without_extension//+([[:space:]])/_}"
        remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_]}"
        change_every_underscore_with_a_single_under_score="${remove_everything_that_is_not_alnum_and_under_score//+(_)/_}"
        new_file_name="$change_every_underscore_with_a_single_under_score.$extension"
        mv -v "$file_name_with_extension" "${new_file_name::80}"
      done
    )
  fi
done < <(find . ! -name . -type d -print0)

创建虚拟目录和文件的脚本。

#!/usr/bin/env bash

mkdir -p foo/bar/baz/more/qux/sux

cd foo/ &&  touch 'Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip'
cd bar/ &&  touch 'Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip'
cd baz/ && touch 'Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].mp4'
cd more/ && touch 'Might and Magic III Character Editor (1991)(Blackbeard'\''s Ghost) [Utility].zip'
cd qux/ && touch 'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip'
cd sux/ && touch 'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].jpg'

使用tree检查目录树

tree foo/
foo/
├── bar
│   ├── baz
│   │   ├── Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].mp4
│   │   └── more
│   │       ├── Might and Magic III Character Editor (1991)(Blackbeard's Ghost) [Utility].zip
│   │       └── qux
│   │           ├── Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip
│   │           └── sux
│   │               └── Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].jpg
│   └── Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip
└── Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip

5 directories, 6 files

使用find 打印文件。

find foo/ ! -name . -type f 

输出是

foo/Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip
foo/bar/Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip
foo/bar/baz/more/Might and Magic III Character Editor (1991)(Blackbeard's Ghost) [Utility].zip
foo/bar/baz/more/qux/sux/Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].jpg
foo/bar/baz/more/qux/Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip
foo/bar/baz/Metaltech- Battledrome Game Editor (1994)(Sierra On-Line, Inc.) [Utility].mp4

在顶级目录中运行脚本会打印如下内容:

Entering directory ./foo
mv -v Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip Mean_18_Golf_Menu_SW_1988Robert_J_Butler_Sports_Golf_Utility.zip
Entering directory ./foo/bar
mv -v Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip Mean_18_M18_1988Ken_Hopkins_Sports_Golf_Utility.zip
Entering directory ./foo/bar/baz
There are no files ending in *.zip here!, moving on...
Entering directory ./foo/bar/baz/more
mv -v Might and Magic III Character Editor (1991)(Blackbeard's Ghost) [Utility].zip Might_and_Magic_III_Character_Editor_1991Blackbeards_Ghost_Utility.zip
Entering directory ./foo/bar/baz/more/qux
mv -v Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip Might_Magic_3_Character_viewereditor_v11_1991Mark_Betz_and_Chris_Lampton_Editor.
Entering directory ./foo/bar/baz/more/qux/sux
There are no files ending in *.zip here!, moving on...

  • 如果您对输出感到满意,请删除 echo,以便 mv 重命名文件。

没有echo,输出类似于:

Entering directory ./foo
renamed 'Mean 18 - Golf Menu [SW] (1988)(Robert J. Butler) [Sports, Golf, Utility].zip' -> 'Mean_18_Golf_Menu_SW_1988Robert_J_Butler_Sports_Golf_Utility.zip'
Entering directory ./foo/bar
renamed 'Mean 18 - M18 (1988)(Ken Hopkins) [Sports, Golf, Utility].zip' -> 'Mean_18_M18_1988Ken_Hopkins_Sports_Golf_Utility.zip'
Entering directory ./foo/bar/baz
There are no files ending in *.zip here!, moving on...
Entering directory ./foo/bar/baz/more
renamed 'Might and Magic III Character Editor (1991)(Blackbeard'\''s Ghost) [Utility].zip' -> 'Might_and_Magic_III_Character_Editor_1991Blackbeards_Ghost_Utility.zip'
Entering directory ./foo/bar/baz/more/qux
renamed 'Might Magic 3 Character viewer-editor v1.1 (1991)(Mark Betz and Chris Lampton) [Editor].zip' -> 'Might_Magic_3_Character_viewereditor_v11_1991Mark_Betz_and_Chris_Lampton_Editor.'
Entering directory ./foo/bar/baz/more/qux/sux
There are no files ending in *.zip here!, moving on...

如果我们可以将不需要的字符序列转换为一个下划线,这会更好。如,而不是:XArchRogueTool(1984)(Unknown)[Utility].zip 输出可以是:

X_Arch_Rogue_Tool_(1984)_(Unknown)_[Utility].zip?

更改remove_everything_that_is_not_alnum_and_under_score的值

来自:

remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_]}"

remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_()\[\]]}" 

排除括号( )和括号[ ]


change_every_underscore_with_a_single_under_score所在的行下面添加代码。

insert_underscore_in_between_parens="${change_every_underscore_with_a_single_under_score//')('/')_('}"

new_file_name=的值改为"$insert_underscore_in_between_parens.$extension"

new_file_name="$insert_underscore_in_between_parens.$extension"

将目录指向脚本需要一些修改。

shebang之后添加下面的代码

directory_to_process="$1"

if [[ ! -e "$directory_to_process" ]]; then
  printf >&2 '%s no such file or directory!\n' "$directory_to_process"
  exit 1
elif [[ ! -d "$directory_to_process" ]]; then
  printf >&2 '%s does not appear to be a directory!\n' "$directory_to_process"
  exit 1
fi

然后将.find更改

find "$directory_to_process" ! -name . -type d -print0

新脚本。

#!/usr/bin/env bash

directory_to_process="$1"

if [[ ! -e "$directory_to_process" ]]; then
  printf >&2 '[%s] no such file or directory!\n' "$directory_to_process"
  exit 1
elif [[ ! -d "$directory_to_process" ]]; then
  printf >&2 '[%s] does not appear to be a directory!\n' "$directory_to_process"
  exit 1
fi

shopt -s extglob nullglob

while IFS= read -rd '' directory; do
  if [[ -e $directory && -x $directory ]] ; then
    (
      printf 'Entering directory %s\n' "$directory"
      cd "$directory" || exit
      files=(*.zip)
      (( ${#files[*]} )) || {
        printf 'There are no files ending in *.zip here!, moving on...\n'
        continue
      }
      for file_name_with_extension in *.zip; do
        extension=${file_name_with_extension##*.}
        file_name_without_extension=${file_name_with_extension%."$extension"}
        change_spaces_to_underscore="${file_name_without_extension//+([[:space:]])/_}"
        remove_everything_that_is_not_alnum_and_under_score="${change_spaces_to_underscore//[![:alnum:]_()\[\]]}"
        change_every_underscore_with_a_single_under_score="${remove_everything_that_is_not_alnum_and_under_score//+(_)/_}"
        insert_underscore_in_between_parens="${change_every_underscore_with_a_single_under_score//')('/')_('}"
        new_file_name="$insert_underscore_in_between_parens.$extension"
        echo mv -v "$file_name_with_extension" "${new_file_name:0:80}"
      done
    )
  fi
done < <(find "$directory_to_process" ! -name . -type d -print0)

现在您将目录作为参数提供给脚本。例如

./script.sh foo/

或绝对路径。

./script.sh /path/to/foo

如果您将脚本添加到您的 PATH 并使其可执行,那么您可以。

script.sh /path/to/foo

假设你的脚本名称是script.sh,你要处理的目录是foo


【讨论】:

  • 哇,哇,哇,伙计们。我打算对第一张海报做出回应——然后还有另外 3 个人在其他方面也做得很好。这真的很有帮助;最后我掌握了如何以及为什么。我非常感谢你们——感激不尽。有了这些信息,我就可以修补一些对我来说非常有用的东西。
  • 很高兴能提供帮助。我很确定其他答案和我的一样好。现在,如果您决定选择答案/解决方案,请查看stackoverflow.com/help/someone-answers
【解决方案2】:

编辑:将不需要的字符序列转换为一个下划线。

我假设当你写“文件名应该只有字符 A-Z 和 1-9”时,你包括小写字母,加上下划线来替换任何不需要的字符序列。我还假设您不希望替换后基名中的前导或尾随下划线。

让我们首先编写一个小 bash 脚本文件,它首先将 zip 文件的路径作为唯一参数 ($1),用 dirname 分隔目录 ($d) 和文件 ($f) 部分和basename,用trsedcut计算新文件名,并重命名文件:

$ cat /mnt/Beers4TB/opendirs/TDC19/renamer.sh
#!/usr/bin/env bash
d="$(dirname "$1")"
f="$(basename -s .zip "$1" | tr -c a-zA-Z1-9 _ | sed 's/__*/_/g' |
    cut -c 1-76 | sed 's/^_//;s/_$//')"
mv "$1" "$d/$f.zip"

接下来,让脚本可执行 (chmod) 并使用 find 遍历层次结构并在每个找到的 zip 文件上调用脚本(首先备份您的文件,以防出现问题):

$ cd /mnt/Beers4TB/opendirs/TDC19
$ chmod +x renamer.sh
$ find . -type f -name '*.zip' -exec ./renamer.sh '{}' \;

(在findexec动作中{}被找到的文件路径替换)。

解释:

  • tr 用于将所有不需要的字符替换为下划线 (_)。选项-c 取指定字符集的补码:

      $ f='!!!Mean 18 - Golf Menu [SW] ('
      $ printf '%s' "$f" | tr -c a-zA-Z1-9 _
      ___Mean_18___Golf_Menu__SW___
    
  • sed 用于仅用一个下划线 (s/__*/_/g) 替换下划线序列,删除前导下划线 (s/^_//) 并删除尾部下划线 (s/_$//):

      $ f="___Mean_18___Golf_Menu__SW___"
      $ printf '%s' "$f" | sed 's/__*/_/g'
      _Mean_18_Golf_Menu_SW_
      $ f="_Mean_18_Golf_Menu_SW_"
      $ printf '%s' "$f" | sed 's/^_//;s/_$//'
      Mean_18_Golf_Menu_SW
    
  • cut 用于将修改后的基本名称剪辑为 80-4=76 个字符。恢复.zip 后缀后最多80 个字符。 cut-c X-Y 选项选择字符编号XY

      $ f='abcdefghi'
      $ printf '%s' "$f" | cut -c 1-4
      abcd
    

【讨论】:

  • 剪切可以替换为f='abcdefghi'; printf '%.4s' "$f"
  • @Paulie420 我编辑了我的答案以添加此内容,但请编辑您的问题以明确这一点。否则未来的读者将无法理解。
  • 如果我们可以将不需要的字符序列转换为一个下划线,这会更好。如,而不是:XArchRogueTool(1984)(Unknown)[Utility].zip 输出会不会是:X_Arch_Rogue_Tool_(1984)_(Unknown)_[Utility].zip?
  • @Paulie420 我已经展示了这一点:只有一个下划线。但是您刚刚再次更改了规范:现在您还想保留括号和方括号。您绝对应该编辑您的问题,使您的规范 100% 清晰并添加输入/输出示例(不是在 cmets 中,在您的问题中)。
【解决方案3】:

我建议您punycode 的名称,但我没有适当的方法(足够的答案)来减少文件的长度以适应 80 个字符的长度(punycode 过程是完全可逆的,并且在它们的位置,给你一个可读的文件名,并且可以修改它以考虑名称字符的字符大小写)

对于额外长度的编码,我会使用某种固定长度的哈希函数来避免名称冲突,但这个过程根本不可逆,你会丢失部分名称。您需要考虑一下您的可能性,以便能够在这方面为您提供帮助。

【讨论】:

    【解决方案4】:

    命令tr -cd 删除所有不在给定列表中的字符。

    for f in *.zip; do
      mv "$f" "$(tr -cd 'A-Za-z0-9. \n' <<< "$f")"
    done
    

    您可以使用sed在相邻的括号之间添加一个空格:

    for f in *.zip; do
      mv "$f" "$(sed 's/)(/ /g' <<< "$f" | tr -cd 'A-Za-z0-9. \n'))"
    done
    

    你可以使用sed来合并多个空格。

    for f in *.zip; do
      mv "$f" "$(sed 's/)(/ /g' <<< "$f" | tr -cd 'A-Za-z0-9. \n' | sed 's/ \+/ /g'))"
    done
    

    【讨论】:

      猜你喜欢
      • 2015-08-02
      • 1970-01-01
      • 2010-12-29
      • 2012-05-19
      • 2016-08-03
      • 2017-09-15
      • 1970-01-01
      • 2020-07-16
      • 2014-07-17
      相关资源
      最近更新 更多