使用特殊字符批量重命名(或正确显示)文件


20

我有一堆目录和子目录,其中包含带有特殊字符的文件,例如以下文件:

robbie@phil:~$ ls testsktest.txt 
test?sktest.txt

查找揭示了一个转义序列:

robbie@phil:~$ find testsktest.txt -ls 
424512 4000 -rwxr--r-x   1 robbie   robbie    4091743 Jan 26 00:34 test\323sktest.txt

我什至可以在控制台上键入其名称的唯一原因是由于制表符的完成。这也意味着我可以手动重命名它们(并去除特殊字符)。

我已将LC_ALL设置为UTF-8,这似乎无济于事(也不在新的shell上):

robbie@phil:~$ echo $LC_ALL
en_US.UTF-8

我正在使用Mac上的ssh连接到计算机。这是Ubuntu安装:

robbie@phil:~$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=7.10
DISTRIB_CODENAME=gutsy
DISTRIB_DESCRIPTION="Ubuntu 7.10"

Shell是Bash,TERM设置为xterm-color。

这些文件已经存在了很长一段时间,并且尚未使用该Ubuntu版本创建。因此,我不知道以前的系统编码设置是什么。

我已经尝试过以下方法:

find . -type f -ls | sed 's/[^a-zA-Z0-9]//g'

但是我找不到能满足我所有需求的解决方案:

  1. 识别所有具有不可显示字符的文件(以上内容忽略太多)
  2. 对于目录树中的所有那些文件(递归),请执行mv oldname newname
  3. (可选)将ä等特殊字符音译为a的能力(不是必需的,但会很棒)

要么

  1. 正确显示所有这些文件(尝试打开它们时在应用程序中没有错误)

我有些零碎的事情,例如遍历所有文件并移动它们,但是识别文件并为mv命令正确格式化它们似乎是困难的部分。

也欢迎提供任何其他有关它们为什么无法正确显示或如何“猜测”正确编码的信息。(我已经尝试过convmv,但是它似乎并没有完全满足我的要求:http : //j3e.de/linux/convmv/


下面的单个答案遵循第一种方法(查找它们并重命名为新的编码),但是第二种方法也将很有趣:现在,当您知道用于远程文件名的编码时,如何以这种方式将其SSH到远程主机一种可以正确显示文件名的方法(并且可以通过使用键盘输入文件名来进行管理)?
imz-伊万·扎哈拉里舍夫(Ivan Zakharyaschev)2011年

Answers:


21

我猜您看到这个无效字符,因为名称包含无效的UTF-8字节序列。典型的unix文件系统(包括您的文件系统)上的文件名是字节字符串,由应用程序决定使用哪种编码。如今,有一种使用UTF-8的趋势,但是它不是通用的,特别是在无法使用纯ASCII且自从UTF-8出现之前就一直使用其他编码的语言环境中。

尝试LC_CTYPE=en_US.iso88591 ls查看文件名在ISO-8859-1(latin-1)中是否有意义。如果不是,请尝试其他语言环境。请注意,LC_CTYPE此处仅语言环境设置很重要。

在UTF-8语言环境中,以下命令将向您显示名称无效的UTF-8的所有文件:

grep-invalid-utf8 () {
  perl -l -ne '/^([\000-\177]|[\300-\337][\200-\277]|[\340-\357][\200-\277]{2}|[\360-\367][\200-\277]{3}|[\370-\373][\200-\277]{4}|[\374-\375][\200-\277]{5})*$/ or print'
}
find | grep-invalid-utf8

您可以使用recodeiconv检查它们在其他语言环境中是否更有意义:

find | grep-invalid-utf8 | recode latin1..utf8
find | grep-invalid-utf8 | iconv -f latin1 -t utf8

一旦确定一堆文件名使用某种编码(例如latin1),重命名它们的一种方法是

find | grep-invalid-utf8 |
rename 'BEGIN {binmode STDIN, ":encoding(latin1)"; use Encode;}
        $_=encode("utf8", $_)'

这使用了Debian和Ubuntu上可用的perl 重命名命令。您可以传递它-n以显示它会做什么,而无需实际重命名文件。


谢谢,今天晚些时候我将尝试其中的一些方法!看起来这将是公认的答案:)
RobbieV

查找| grep'[[:print:]]'命令似乎只是返回所有文件。UTF-8是否应该与许多其他带有“正常”字符的编码兼容?
RobbieV

@RobbieV:我打错了字,意grep [^[:print:]]在寻找无法打印的字符。但是我刚刚使用GNU grep进行了测试,并且没有发现无效的UTF-8序列[^[:print:]](这是有道理的,因为它们不是不可打印的字符,根本不是字符)。我使用更长的方法来编辑帖子,其中包含无效的utf8序列。请注意,我还固定了recodeiconv示例的方向。
吉尔(Gilles)'所以

那很好。尝试了除iconv以外的所有命令,它们均按预期工作。纯魔术!
RobbieV

甚至建议的latin1编码都是正确的编码:)
RobbieV

1

我知道这是一个老问题,但是我整夜都在寻找类似的解决方案。我找到了一些有用的技巧,但它们并没有完全满足我的需要,因此我不得不混合搭配一些才能获得我想要的正确结果

只需删除特殊字符并将其替换为(。)点

for f in *.txt; do mv "$f" `echo $f | sed "s/[^a-zA-Z0-9.]/./g"`; done

在cronjob中使用我每分钟都会执行以下操作

*/1 * * * * cd /path/to/files/ && for f in *.txt; do mv "$f" `echo $f | sed "s/[^a-zA-Z0-9.]/./g"`; done >/dev/null 2>&1

我希望有人觉得这对我有所帮助,因为它帮助了我:


(1)为清楚起见,您可能需要更改`…`$(…)—参见thisthisthis。(2)"$f"除非您有充分的理由不这样做,否则您应该始终引用shell变量引用(例如),并且您确定自己知道自己在做什么。这甚至适用于echo "$f" | sed …。它也适用于整个$(…)(或`…`)表达式;即mv "$f" "$(echo "$f" | sed "…")"。…(续)
斯科特

(续)…(3)您应该说,以防止以开头的文件名。(4)如果您有名为“ foo♥bar.txt”和“ foo♠bar.txt”的文件,这将(尝试)将它们重命名为“ foo.bar.txt”,这可能会导致除文件被销毁。(5)为什么您想每分钟这样做一次?mv -- "$f" …-
斯科特,

我有一个洪流脚本,可以自动下载文件。有时某些文件中包含字符,这些字符会导致上传器无法正常运行。因此,通过简单地使用特殊字符重命名文件,我的cron就解决了我所有的问题,并且上传程序顺利完成了工作。
Topps70'6

因此(此文件tha,t原为-down_loaded.ext)变成(this.fi.le.tha.t.was.down.loaded.ext)
Topps70 2013年

0

现在,当你知道哪个编码用于文件名的远端(在“latin1” -根据意见的第一个答案),你也可以跟随第二路 -运行本地termninal和SSH在这样的方式的远程文件名正确显示(而不是第一种方式:它们重命名)

一样,您可以在本地启动可以使用这种特殊编码的终端,如下所示:

LC_ALL = zh_CN.latin1 xvt&

xvt 代表您的终端程序。

也许,现有的语言环境称为en_US.iso88591,而不是en_US.latin1我假设的。


0

这不满足批量要求,但是我遇到了一个类似的问题,即我有一个文件的多个版本,这些文件的名称相似,只是一个奇怪的字符不同。不幸的是,这意味着我无法使用我通常使用的通配符来重命名罪犯。

最后,我使用Filezilla作为SFTP客户端进行连接,浏览到文件并使用GUI重命名了文件。Filezilla很好地处理了躲避字符。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.