递归“规范化”文件名

5

我的意思是在文件名中去除特殊字符等。

我写了一个可以递归重命名文件的脚本[http://pastebin.com/raw.php?i=kXeHbDQw]:

例如,之前的文件名为:

THIS i.s my file (1).txt

运行脚本后:
This-i-s-my-file-1.txt

好的,这是需要翻译的内容:

好的,这就是它:

但是:当我想要完全测试它时,使用像这样的文件名:

¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÂÃÄÅÆÇÈÊËÌÎÏÐÑÒÔÕ×ØÙUÛUÝÞßàâãäåæçèêëìîïðñòôõ÷øùûýþÿ.txt
áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&'()*+,:;<=>?@[\]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£.txt

它失败了 [http://pastebin.com/raw.php?i=iu8Pwrnr]:

$ sh renamer.sh directorythathasthefiles
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£': No such file or directory
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£': No such file or directory
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£': No such file or directory
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£': No such file or directory
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£': No such file or directory
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£': No such file or directory
mv: cannot stat `./áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†....and so on
$

所以 "mv" 无法处理特殊字符.. :\

我已经花费了许多小时来解决这个问题..

有人有一个可行的方法吗?[可以处理这两行中的字符[文件名]的方法?]


5
“Accepted answer rate of zero”(零采纳答案率)不会让您的个人资料看起来很好。 - user1686
4
请不要进行交叉发布(cross-post),详情请参见此链接:http://serverfault.com/questions/223514/recursively-normalize-filenames。 - Dennis Williamson
2
为什么从超级用户迁移?这是Shell脚本编写,不是编程... - leppie
考虑使用tr而不是sed。用tr会节省很多行代码。 - J-16 SDiZ
@grawity:你可以继续相信那个...我会坚持写真正的代码。 - leppie
@grawity:注意:我说的是shell脚本编程。 - leppie
4个回答

18

mv 处理特殊字符没问题。而你的脚本则不行。


具体来说:

  1. 你正在使用 find 查找所有目录,然后分别对每个目录使用 ls 命令。

    1. 如果可以用 一个 命令完成相同的任务,为什么要使用 for DEPTH in...

find -maxdepth 100 -type d
  • 这使得任意深度限制变得不必要

  • find -type d
    
  • 千万不要解析ls命令的输出结果,尤其是如果你可以让find命令来处理的话。

  • find -not -type d
    
  • 确保它在最糟糕的情况下也能正常工作:

  • find -not -type d -print0 | while read -r -d '' FILENAME; do
    

    这可以阻止read处理特定的转义序列,避免在文件名中包含换行符时出现错误。

  • 你正在为每个字符重复执行整个ls | replace循环。 不要这样做,它会影响性能。循环遍历所有文件一次,只使用多个sed或在一个sed命令中进行多个替换。

  • sed 's/á/a/g; s/í/i/g; ...'
    

    (我原本想建议使用 sed 'y/áí/ai/',但不幸的是它似乎不能处理Unicode。也许可以使用 perl -CS -Mutf8 -pe 'y/áí/ai/'。)

  • 你仍在用ASCII思考:"其他特殊字符-ASCII码33.. ..255",请不要这样。

    1. 如今,大多数系统都使用UTF-8编码的Unicode,它具有更广泛的"特殊"字符范围——大到一一列出变得毫无意义。(甚至是多字节——"e"是一个字节,"ė"是三个字节。)

    2. 真正的ASCII只有128个字符。你目前想到的可能是ISO 8859字符集(有时称为"ANSI")——特别是ISO 8859-1。但它们一直延续到8859-16,只有"ASCII"部分保持不变。

  • echo -n $(command)相当无用。

  • 有更简单的方法来查找给定路径的目录和基本名称。例如,你可以这样做:

    directory=$(dirname "$path")
    oldnname=$(basename "$path")
    # filter $oldname
    mv "$path" "$directory/$newname"
    
  • 不要使用egrep来检查错误。检查程序的返回代码。(就像您已经对cd所做的那样。)

  • 而且,不要筛选其他错误,而是...

  • if [[ -e $directory/$newname ]]; then
        echo "target already exists, skipping: $oldname -> $newname"
        continue
    else
        mv "$path" "$directory/$newname"
    fi
    
  • 可以将大量的 sed 's/------------/-/g' 调用更改为单个正则表达式:

  • sed -r 's/-{2,}/-/g'
    
  • tr [foo] [bar]中的[ ]是不必要的,它们只会导致tr[替换为[]替换为]

  • 真的吗?

  • echo "$FOLDERNAME" | sed "s/$/\//g"
    

    那这个怎么样?

    echo "$FOLDERNAME/"
    

    最后,使用detox


    6
    为了处理那个混乱的内容,给你加10分。为了排毒再加10分。不幸的是,tr也不能处理Unicode。虽然grep理解等价类([[=a=]]匹配'aàâãäå'),但sedtrgawk好像都不行。 - Dennis Williamson
    @Dennis:GNU的sed支持[[=a=]] - user1686

    6
    尝试类似以下内容:
    find . -print0 -type f | awk 'BEGIN {RS="\x00"} { printf "%s\x00", $0; gsub("[^[:alnum:]]", "-"); printf "%s\0", $0 }' | xargs -0 -L 2 mv
    

    使用xargs(1)可以确保每个文件名作为一个参数传递。使用awk(1)将新文件名添加到旧文件名后面。
    还有一个技巧:sed -e 's/-+/-/g' 将多个"-"替换为一个"-"。

    2
    好的,这是 awk 和 xargs 的性感用法。 - MikeyB

    4
    假设你的脚本其余部分正确,你的问题在于你使用了read,但是你应该使用read -r。请注意反斜杠已经消失:
    áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&'()*+,:;<=>?@[\]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£.txt
    áíüűúöőóéÁÍÜŰÚÖŐÓÉ!"#$%&\'()*+,:;<=>?@[]^_`{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£
    

    1

    唉...

    一些清理脚本的技巧:

    ** 使用sed同时对多个字符进行转换,这将使代码更加整洁,易于管理:

    dev:~$ echo 'áàaieeé!.txt' | sed -e 's/[áàã]/a/g; s/[éè]/e/g'
    aaaieee!.txt
    

    与其为每个更改重命名文件,不如运行所有过滤器,然后进行一次移动

    $ NEWNAME='áàaieeé!.txt'
    $ NEWNAME="$(echo "$NEWNAME" | sed -e 's/[áàã]/a/g; s/[éè]/e/g')"
    $ NEWNAME="$(echo "$NEWNAME" | sed -e 's/aa*/a/g')"
    $ echo $NEWNAME
    aieee!.txt
    

    与其使用 ls | read ... 循环,不如使用:

    for OLDNAME in $DIR/*; do
      blah
      blah
      blah
    done
    

    ** 将您的路径遍历和重命名逻辑分别拆分为两个脚本。一个脚本查找需要重命名的文件,另一个脚本处理单个文件的规范化。一旦您学会了“find”命令,您就会意识到可以放弃第一个脚本 :)


    也许你的意思是 "for OLDNAME in "$DIR"/*。 - marco
    @marco:称其为伪代码吧 :) 实际上,我删除了它,试图记住如何解决代码块在列表 markdown 中的错误。 - MikeyB

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接