如何批量重命名文件名,使其不包含与其他文件系统冲突的字符,例如,
Screenshot 2015-09-07-25:10:10
请注意,此文件名中的冒号是问题所在。Windows 或 Mac 不会解析这些冒号。
这些文件可以重命名为
Screenshot 2015-09-07-25--10--10
我必须将大量文件从 Ubuntu 移动到另一个操作系统。我使用 Rsync 将它们复制到 NTFS 驱动器,但这样会丢失一些文件。我还将它们复制到 ext4 驱动器。
以下列表是保留字符:
< (less than)
> (greater than)
: (colon)
" (double quote)
/ (forward slash)
\ (backslash)
| (vertical bar or pipe)
? (question mark)
* (asterisk)
另一个问题是,Windows 的文件名不区分大小写(大多数 OS X 系统也是如此)。
答案1
你可以做类似的事情:
rename 's/[<>:"\\|?*]/_/g' /path/to/file
这会将所有这些字符替换为_
。请注意,您不需要替换/
,因为它是两个文件系统中文件名的无效字符,但用作 Unix 路径分隔符。使用以下内容扩展到目录及其所有内容:
find /path/to/directory -depth -exec rename 's/[<>:"\\|?*]/_/g' {} +
请注意,/
(标记模式的结束)和\
都经过转义。为了保持唯一性,您可以向其附加一个随机前缀:
$ rename -n 's/[<>:"\/\\|?*]/_/g && s/^/int(rand(10000))/e' a\\b
a\b renamed as 8714a_b
更完整的解决方案至少应该:
- 将所有字符转换为相同大小写
- 使用合理的计数系统
也就是说,foo.mp3
不应该变成foo.mp3.1
,但是foo.1.mp3
,因为 Windows 更依赖于扩展。
考虑到这一点,我编写了以下脚本。我尝试使用前缀路径来复制重命名的文件,而不是修改原始文件,从而实现非破坏性。
#! /bin/bash
windows_chars='<>:"\|?*'
prefix="windows/"
# Find number of files/directories which has this name as a prefix
find_num_files ()
(
if [[ -e $prefix$1$2 ]]
then
shopt -s nullglob
files=( "$prefix$1-"*"$2" )
echo ${#files[@]}
fi
)
# From http://www.shell-fu.org/lister.php?id=542
# Joins strings with a separator. Separator not present for
# edge case of single string.
str_join ()
(
IFS=${1:?"Missing separator"}
shift
printf "%s" "$*"
)
for i
do
# convert to lower case, then replace special chars with _
new_name=$(tr "$windows_chars" _ <<<"${i,,}")
# if a directory, make it, instead of copying contents
if [[ -d $i ]]
then
mkdir -p "$prefix$new_name"
echo mkdir -p "$prefix$new_name"
else
# get filename without extension
name_wo_ext=${new_name%.*}
# get extension
# The trick is to make sure that, for:
# "a.b.c", name_wo_ext is "a.b" and ext is ".c"
# "abc", name_wo_ext is "abc" and ext is empty
# Then, we can join the strings without worrying about the
# . before an extension
ext=${new_name#$name_wo_ext}
count=$(find_num_files "$name_wo_ext" "$ext")
name_wo_ext=$(str_join - "$name_wo_ext" $count)
cp "$i" "$prefix$name_wo_ext$ext"
echo cp "$i" "$prefix$name_wo_ext$ext"
fi
done
实际操作:
$ tree a:b
a:b
├── b:c
│ ├── a:d
│ ├── A:D
│ ├── a:d.b
│ └── a:D.b
├── B:c
└── B"c
└── a<d.b
3 directories, 5 files
$ find a:b -exec ./rename-windows.sh {} +
mkdir -p windows/a_b
mkdir -p windows/a_b/b_c
mkdir -p windows/a_b/b_c
cp a:b/B"c/a<d.b windows/a_b/b_c/a_d.b
mkdir -p windows/a_b/b_c
cp a:b/b:c/a:D.b windows/a_b/b_c/a_d-0.b
cp a:b/b:c/A:D windows/a_b/b_c/a_d
cp a:b/b:c/a:d windows/a_b/b_c/a_d-1
cp a:b/b:c/a:d.b windows/a_b/b_c/a_d-1.b
$ tree windows/
windows/
└── a_b
└── b_c
├── a_d
├── a_d-0.b
├── a_d-1
├── a_d-1.b
└── a_d.b
2 directories, 5 files
该脚本可在我的 Github 仓库。
答案2
以递归方式将文件名中的字符串或字符列表替换为其他字符串或字符
下面的脚本可用于将文件名中可能出现的字符串或字符列表替换为任意的每串。由于脚本只重命名文件本身(而不是路径),因此不存在混淆目录的风险。
替换在列表中定义:(chars
见下文)。可以为每个字符串提供自己的替换,以便能够逆转重命名(如果您愿意的话)。(假设替换是唯一的字符串)。如果您想用下划线替换所有有问题的字符串,只需定义列表如下:
chars = [
("<", "_"),
(">", "_"),
(":", "_"),
('"', "_"),
("/", "_"),
("\\", "_"),
("|", "_"),
("?", "_"),
("*", "_"),
]
欺骗
为了防止名称重复,脚本首先创建“新”名称。然后检查同一目录中是否已存在类似名称的文件。如果存在,它会创建一个新名称,前面加上dupe_1
或dupe_2
,直到找到该文件的“可用”新名称:
变成:
剧本
#!/usr/bin/env python3
import os
import shutil
import sys
directory = sys.argv[1]
# --- set replacement below in the format ("<string>", "<replacement>") as below
chars = [
("<", "_"),
(">", "_"),
(":", "_"),
('"', "_"),
("/", "_"),
("\\", "_"),
("|", "_"),
("?", "_"),
("*", "_"),
]
# ---
for root, dirs, files in os.walk(directory):
for file in files:
newfile = file
for c in chars:
newfile = newfile.replace(c[0], c[1])
if newfile != file:
tempname = newfile; n = 0
while os.path.exists(root+"/"+newfile):
n = n+1; newfile = "dupe_"+str(n)+"_"+tempname
shutil.move(root+"/"+file, root+"/"+newfile)
如何使用
- 将脚本复制到一个空文件中,保存为
rename_chars.py
。 - 如果您需要替换列表,请进行编辑。事实上,scrip0t 会用下划线替换所有出现的问题字符,但选择权在您手中。
通过以下命令在目录上进行测试运行:
python3 /path/to/rename_chars.py <directory_to_rename>
笔记
请注意以下行:
("\\", "_bsl_"),
在 python 中,反斜杠需要用另一个反斜杠进行转义。
答案3
我认为上述解决方案可能有效,但您还有另一种情况:ext 文件系统允许更长的文件名和文件夹名称。您还必须考虑名称的长度,并使用某种算法对它们进行分叉,在名称重复的情况下在末尾添加数字或其他内容。