用惯了鼠标在图形界面里右键改名,一进到纯命令行的 linux 环境,不少人是真不习惯。最直接的表现就是:想改个文件名,第一反应是 mv ,但用完又总觉得这不就是"移动"文件吗,和"重命名"有啥关系。除了 mv 还有没有别的办法?批量改文件名怎么办?文件名是中文结果从 windows 传过来变成乱码怎么处理?这些问题我相信每个用 linux 的人都碰到过。
这篇就专门聊聊 linux 下修改文件名的三种主流方法,最基础的 mv 到批量神器 rename ,再到用 shell 循环和 find 组合处理复杂场景,最后补上大家问得最多的乱码文件名处理和几个高频踩坑场景。内容不会太深,但保证每个方法你都能直接拿去用,理解背后的原理之后,以后遇到再刁钻的文件名都不会慌。
1. mv 命令:最基础也最容易忽略的改名方式
很多教程把 mv 描述成"移动文件或目录",这个说法没错,但不够直观。实际上在 linux 的文件系统层面,移动和重命名是同一个操作: 路径的变更 。你从 /home/user/a.txt 变成 /home/user/b.txt ,本质就是把文件从旧路径挪到新路径,文件名也随之改变。所以用 mv 改名的完整写法是:
mv [选项] 源文件或目录 目标文件名
1.1 mv 改名的基本操作与参数
先看最简单的单个文件改名:
mv oldname.txt newname.txt
这命令的意思是把 oldname.txt 这个文件在同一个目录下的路径从旧名字换成新名字,结果就是完成了重命名。
如果是跨目录改动,同时也会把文件挪走,注意目标路径的写法:
# 把 old.txt 从当前目录移动到 /home/user/docs/ 下,同时改名为 new.txt mv old.txt /home/user/docs/new.txt # 如果只想移动到别的目录,保留原名 mv old.txt /home/user/docs/
记住一个关键点: mv 的目标如果写的是 以 / 结尾的目录路径 ,那就只是移动不改名;目标如果写的是 完整的新文件名路径 ,那就是移动加改名。
mv 有几个参数实际用的时候比较顺手:
| 参数 | 作用 | 说明 |
|---|---|---|
-i | 交互式 | 目标文件已存在时,会询问是否覆盖 |
-n | 不覆盖 | 目标文件已存在时不执行操作,直接跳过 |
-v | 显示过程 | 输出"重命名/移动"的详细日志 |
-b | 备份 | 目标文件存在时先备份一份,再覆盖 |
注意: -n 和 -i 是互斥的,同时写的时候以后面的那个为准。建议日常养成习惯加 -i 或者 -n ,尤其是批量操作时,能避免不小心覆盖掉重要文件。
我个人的习惯是封装一个别名,在 ~/.bashrc 里加上:
alias mv='mv -i'
这样每次执行 mv 默认就会询问是否覆盖,心里踏实很多。毕竟覆盖操作在 linux 里是没有回收站可言的,文件一旦覆盖基本就找不回来了。
1.2 用通配符配合 mv 实现简单的批量改名
很多人以为 mv 只能改一个文件,其实配合通配符,它能处理一些简单的批量场景,只是没有真正的批处理命令那么优雅。
比如当前目录下有一批 .txt 文件,你想给它们统一加上 _bak 后缀:
for file in *.txt; do mv "$file" "${file%.txt}_bak.txt"; done
这行看着像是 shell 循环,的确不算纯 mv ,但核心还是 mv 在干活。如果你只是想让某个目录下所有 .log 文件都改成 .txt 后缀,可以配合 bash 的字符串替换:
for file in *.log; do mv "$file" "${file%.log}.txt"; done
这里的 ${file%.log} 是 bash 的变量模式匹配,意思是去掉变量 file 末尾的 .log 部分,然后再拼上新的 .txt 。这属于 bash 的基础字符串处理,推荐花十分钟学一下,实用性极高。
不过要提醒一句:如果只是简单的后缀替换,用下文介绍的 rename 命令会快得多。 mv 的批量能力更适合那种规则不统一、只能靠循环逐个处理的情况。
1.3 特殊字符文件名的移动与改名
这是新手最容易翻车的点。当文件名里含有空格、中文、括号、 & 符号时,直接写进命令经常出错。
比如有个文件名是 my file.txt ,直接执行:
mv my file.txt my_file.txt
系统会把 my 和 file.txt 当成两个参数,结果大概率是报错或者把文件搞乱。正确做法是把文件名用引号包起来:
mv "my file.txt" "my_file.txt"
单引号也可以,区别在于单引号内部不会解析变量和特殊符号,双引号会。如果文件名本身包含单引号,就比较棘手,这时候用 tab 键自动补全是最稳的——输入前几个字符然后按 tab,shell 会帮你自动转义特殊字符。
经验之谈:在命令行里面对特殊字符文件名,我几乎从不用手拼完整名字,永远是输入开头几个字母然后 tab 补全,让 shell 自己处理转义。这比你自己加任何引号都可靠。
2. rename 命令:批量重命名的利器
如果你需要批量修改文件名, rename 才是真正的主角。但由于 linux 发行版不同, rename 命令有 两个完全不同的版本 ,语法差异巨大,很多人在这里踩坑。
2.1 两种版本的 rename:perl 版与 util-linux 版
先说结论:debian/ubuntu 系默认装的是 perl 版 ,red hat/centos 系默认装的是 util-linux 版(也叫 c 版) 。
你可以在终端输入 rename --version 来快速判断:
# perl 版输出(ubuntu/debian): /usr/bin/rename file::rename version 1.10 # c 版输出(centos/rhel): rename from util-linux 2.23.2
这两个版本的语法完全不同,混着用会直接报错,这是网上搜命令经常搜到"看起来没错但就是跑不起来"的最常见原因。
perl 版的基本语法是:
rename 's/旧内容/新内容/' 文件名...
这是用 perl 正则表达式的 s/// 替换模式。c 版的语法则是:
rename 旧字符串 新字符串 文件名...
就是简单的字符串替换,不支持正则。
2.2 perl 版 rename 的常见批量场景详解
因为 perl 版功能更强,我重点讲它。在 ubuntu/debian 系系统上, rename 几乎是无敌的批量改名工具。它接受的第一个参数是 perl 的替换表达式,后面跟要处理的文件列表,支持通配符。
场景一:把所有 .txt改成 .md
rename 's/\.txt$/\.md/' *.txt
这里的 \. 表示一个点(在正则里点是通配符,必须转义), $ 表示字符串末尾, s/旧/新/ 就是替换。执行完目录下所有 txt 文件都会变成 md 后缀。
场景二:把文件名中的空格替换成下划线
rename 's/ /_/g' *
这里的 g 表示全局替换,一个文件名里有多个空格也会一次性全替换掉。 * 代表当前目录下所有文件。这个命令在处理从网上下载的文件名带空格的情况时特别好用。
场景三:批量给文件加前缀或后缀
# 给所有 .jpg 文件加 img_ 前缀
rename 's/^/img_/' *.jpg
# 给所有文件加 _2024 后缀(在扩展名前插入)
rename 's/\.(jpe?g|png|gif)$/_2024.$1/' *.{jpg,jpeg,png,gif}
第二个命令稍微复杂一些, (...) 是分组, $1 引用第一个分组,意思是匹配三种图片格式,在扩展名前插入 _2024 。这样 photo.jpg 会变成 photo_2024.jpg 。
场景四:按数字序号批量改名
临时要给一批文件按序号重命名,perl 版 rename 的强大就体现出来了,它可以直接调用 perl 代码块来生成序号:
j=1; for i in *.jpg; do mv "$i" "$(printf 'img_%03d.jpg' $j)"; j=$((j+1)); done
虽然这是循环加 mv 的组合,但它展示了 perl 与 shell 结合的处理思路。如果你想用纯 perl 的 rename 实现递增序号,需要嵌入 $++ 这样的计数变量,写法是:
rename 's/.*/sprintf("img_%04d.jpg", ++$count)/e' *.jpg
那个 e 结尾表示把替换部分当作 perl 代码执行。这个命令在纯 perl 版 rename 上是可行的,但我得说实话,这种写法可读性比较差,容易把自己绕晕。如果追求清晰,用前面那种 shell 循环加 mv 的方式反而更直白。
2.3 c 版 rename 的使用与限制
如果你用的是 centos 或者从 red hat 系继承的系统, rename 默认是 c 版。它的用法特别直观:
# 把文件名中的 old 替换成 new rename old new *.txt # 把后缀 .txt 改成 .bak rename .txt .bak *.txt
它比 perl 版简单,但有个明显问题: 不支持正则表达式 ,只是纯粹的字符串替换。而且它有个怪癖:如果旧字符串在新文件名里找不到,会直接报 rename: 无法将...重命名 错误,严格匹配,这有时反而让人觉得刻板。
解决跨发行版兼容的一种思路:在脚本里先判断系统类型或者 rename 的版本,再决定执行哪个分支。但更实际的做法是:既然 perl 版功能更强,在低配环境里可以直接用 perl 版替代,部分系统需要你自行安装:
# debian/ubuntu sudo apt install rename # centos/rhel 系列如果需要 perl 版可以用 cpan 安装 file::rename # 或者直接下载源码编译,不过日常使用频率不高的话,不一定值得折腾
重点提醒:写脚本给别人用的时候,一定不要默认大家的 rename 都是 perl 版。最稳妥的方法是在脚本里加一句判断, rename --version 的输出里如果包含 file::rename 就是 perl 版,否则按 c 版处理。
3. shell 循环与 find 组合:处理更复杂的重命名场景
mv 管单个, rename 管批量替换。但有一类场景它们两个都未必好使:目标文件分散在各个子目录里,或者改名规则特别复杂(比如按文件的修改时间做格式化重命名),又或者需要找出文件名包含特定关键词的文件并逐一修改。
这时候要把 find 、 while 循环和 mv 组合起来。
3.1 用 find 定位目录树中的目标文件再批量改名
find 是 linux 下查找文件的大杀器,它最大的优势是能递归处理目录树,并且支持按类型、按时间、按大小、按权限等多种条件组合过滤。
比如你想把整个 /data/logs 目录下所有的 .log 文件,全部改成 .txt 后缀:
find /data/logs -type f -name "*.log" | while read file; do
mv "$file" "${file%.log}.txt"
done
这行命令做了三件事:
find /data/logs -type f -name "*.log"找出所有后缀是.log的文件;- 通过管道
|把这些文件列表逐行传给while read循环; - 循环里用
${file%.log}.txt去掉.log后缀再拼接成新文件名交给mv。
有人会问,直接用 find ... -exec mv ... 不也行吗?当然可以,比如说:
find /data/logs -type f -name "*.log" -exec bash -c 'mv "$0" "${0%.log}.txt"' {} \;
这种写法对文件名里的空格处理得更安全,因为它是通过参数传递而不是通过读取行内容来处理的。 但 exec 方式写复杂逻辑时不方便加判断条件 ,比如某些文件要改、某些不改,或者新文件名需要通过日期函数计算,这时候 while 循环的形式显然更容易扩展。
3.2 利用 bash 字符串处理操作实现高自由度改名
bash 本身内置了不少字符串处理能力,这也是很多人忽略的宝藏。因为 find 和 mv 只是工具,真正定义"新旧文件名对应关系"的往往是这些 shell 原生的变量操作。
几个常用操作:
file="photo.jpg"
# 去掉最短匹配的后缀(从左向右匹配到第一个 . 就停)
echo "${file%.*}" # 输出 photo
# 去掉最长匹配的后缀(从左向右匹配到最后一个 . 才停)
echo "${file%%.*}" # 输出 photo
# 去掉最短匹配的前缀(从右向左匹配到第一个 . 就停)
echo "${file#*.}" # 输出 jpg
# 去掉最长匹配的前缀(从右向左匹配到最后一个 . 才停)
echo "${file##*.}" # 输出 jpg
# 全局替换
echo "${file/jpg/png}" # 输出 photo.png
组合这些操作,就可以实现很多看似复杂的改名逻辑。
举个例子,把目录下的照片文件按照修改日期重命名:
for file in *.jpg; do
timestamp=$(stat -c "%y" "$file" | cut -d' ' -f1)
mv "$file" "${timestamp}_${file}"
done
stat -c "%y" 输出文件的修改时间, cut -d' ' -f1 只取日期部分,最终结果是文件被改名为 2024-06-18_photo.jpg 这样的格式。逻辑本身简单,但用到的几个命令拼在一起就具备了真实的生产力。
再比如,你有大量文件名是 report_final_v2.docx 、 report_final_v3.docx 这样的,现在想把 final_ 去掉:
for file in *.docx; do
mv "$file" "${file/final_/}"
done
一个字都不用多写, ${file/final_/} 就是把变量里第一个 final_ 替换成空字符串。
3.3 处理文件名乱码与 gbk/utf-8 编码转换的实操方案
这个场景看着和"改名"关系不大,但在实际操作中,很多人改名的目的恰恰是因为文件名乱码了。最常见的情况是:windows 下用 winrar 或 360 压缩打包的文件,文件名是 gbk 编码,传到 linux 下一解压,中文全变成乱码,类似 绋炵悊淇℃伅.txt 。
这时候如果你直接 mv 绋炵悊淇℃伅.txt 合理信息.txt ,一个一个改也不是不行,但如果文件很多就特别痛苦。
推荐方案:用 convmv 工具批量转换文件名编码,这是专门干这个活的小工具。
# 安装 convmv sudo apt install convmv # debian/ubuntu sudo yum install convmv # centos/rhel # 先将子目录内所有文件名从 gbk 转为 utf-8(先预览,不实际执行) convmv -f gbk -t utf-8 --notest -r /path/to/dir
参数说明:
-f gbk:源编码为 gbk;-t utf-8:目标编码为 utf-8;-r:递归处理子目录;--notest:不加这个参数时,convmv 只打印转换预览而不实际修改,加上之后才真正执行改名。
这个命令实际做的工作就是"按编码转换后的新字符串重命名文件",本质上还是批量改名。如果你对编码转换没有把握, 强烈建议先不加 --notest 跑一遍预览 ,看到输出符合预期再加参数执行。
还有一种思路是用 iconv 转换文件名编码,但需要写一个比较复杂的循环脚本:
for file in *; do
newname=$(echo "$file" | iconv -f gbk -t utf-8 2>/dev/null)
if [ -n "$newname" ] && [ "$file" != "$newname" ]; then
mv "$file" "$newname"
fi
done
这个脚本的原理是把文件名当作字符串通过 iconv 转码,得到新名后执行 mv 。不过 convmv 已经把这个逻辑封装好了,个人建议直接上 convmv ,省时省力,还内置了预览和安全检查。
4. 三种方法的横向对比与选型建议
前面把三种方法都拆开讲了,这一节用表格横向对比一下,方便遇到实际场景时快速选定方案。
| 方法 | 适用场景 | 优点 | 缺点 | 上手难度 |
|---|---|---|---|---|
mv 命令 | 单个文件改名、移动改名 | 系统自带、参数简单、记忆成本低 | 批量需要配循环,规则复杂时脚本冗长 | 极低 |
rename 命令 | 批量替换固定模式(后缀、前缀、连续编号) | 一条命令搞定大量文件,支持正则,效率极高 | 存在 perl/c 两种版本语法不兼容;perl 版表达式需要一点正则基础 | 中 |
find + shell 循环 + mv | 跨目录、规则复杂、需要条件判断的改名 | 最灵活,任何规则都能写出来,可读性强 | 代码较多,遇到特殊字符时要额外小心引号 | 中高 |
个人建议的选型逻辑很简单:
- 改一两个文件,用
mv; - 批量改一些规则明确的后缀、分隔符,尽量学一下 perl 版
rename,效率提升最明显; - 文件分布有多级目录,或者改名规则需要结合时间戳、权限、文件内容等外部条件判断,老老实实用
find加循环。
别迷信某一种工具是"万能"的,在 linux 命令行里不存在万能的命令,组合、搭配才是常态。比如 rename 的命令前面也可以接 find 来限定范围:
find . -type f -name "*.tmp" -exec rename 's/\.tmp$//' {} \;
这其实已经是两个工具的协作模式了,你会发现一旦习惯这种组合思路,很多问题会迎刃而解。
重要提醒:任何涉及批量改名的操作,尤其是目录结构复杂、文件数量大的时候,建议先做一个 ls 的备份清单,把当前文件名记录到文件里,万一改名方向搞错了还能对照恢复。
# 把当前目录下所有文件名保存到清单
ls -1 > file_list_before.txt
# 也可以生成一个完整的"旧名 -> 新名"对应关系的恢复脚本
for file in *.txt; do
echo "mv \"${file%.txt}.md\" \"$file\"" >> rollback.sh
done
这个"留后路"的习惯,我建议每个人从一开始就养成。linux 没有 ctrl+z 撤销文件操作的能力,一旦批量命令跑错,可能就是大规模损失。
5. 高频踩坑场景与问题排查实录
写这节之前我翻了一下大家在评论区、交流群里问得最多的问题,基本集中在下面几个场景。每个都是真实操作中会出的问题,我按"问题 - 原因 - 解决"的结构整理一下。
5.1 为什么 mv 和 rename 明明是英文命令,中文文件名还是被改坏?
这种情况大概率是 终端字符编码和文件系统编码不一致 。比如你 ssh 连接到服务器,本地终端用的是 utf-8,但服务器上某个目录是通过 windows 共享挂载过来的,文件名编码可能是 gbk。你在终端里看到的中文可能已经是"显示正常但底层字节不同"的状态。
排查方法:在文件所在的目录执行 ls -b ,这个参数会把文件名里的不可见字符和八进制转义直接打印出来,如果输出里出现一堆 \345\256\236 之类的编码,说明底层的字节流并不是 utf-8 的标准编码。
解决办法:尽量统一环境编码,让服务器端 locale 和终端编码一致:
# 查看当前语言环境 echo $lang # 临时切换到 utf-8 export lang=en_us.utf-8
如果文件已经存成 gbk 的文件名,用前面说的 convmv 转一下编码再继续操作。
5.2 执行 rename 批量改后缀时报错 "unknown option: s"
这个报错非常典型,一眼就能判断出你用的系统是 c 版 rename 。c 版不认 's/.../.../' 这种 perl 正则表达式的语法。
解决方案有两条路:
- 改用 c 版的语法:
rename .txt .md *.txt; - 安装 perl 版 rename:
sudo apt install rename(debian/ubuntu)。
如果你在 centos 上想勉强实现"把所有 .txt 改成 .md ",c 版直接:
rename .txt .md *.txt
没有正则,但纯字符串替换已经覆盖了大部分场景。
5.3 批量改名的过程中途报错停止,怎么继续?
rename 在遇到某个文件不符合预期时会报错,但已经处理完成的文件不会被回滚。如果你发现命令跑到一半停了,检查具体报错信息,修正规则后重新执行一次就行。已经改名成功的文件不会再次匹配,所以重复执行是安全的。
但 mv 就不一样了,如果你的循环脚本里有覆盖逻辑,重复执行可能把已经很完美的目标文件再次覆盖掉。所以我在循环脚本里总是加 -i 或者 -n 参数,避免二次执行时造成意外覆盖:
for file in *.bak; do
mv -n "$file" "${file%.bak}.txt"
done
-n 会跳过目标已存在的文件,重复执行完全安全。
5.4 git 仓库中修改文件名导致历史记录丢失怎么办?
这个场景属于进阶问题,但问的人真不少。在 git 管理下,你用 mv 改文件名和在文件管理器里重命名没什么区别,git 能感知到这次变更。但如果你是先删了旧文件再新建新文件,git 很可能认为你删除并新增了一个文件,历史记录就丢失了。
正确的改名方式:直接用 mv 改名,然后 git add -a 暂存变化,git 的相似度检测算法(默认是对内容做相似度比较)会自动识别为 rename(r100)。如果希望更稳妥,用 git mv :
git mv oldname.txt newname.txt
这个命令相当于 mv 加 git rm 加 git add 的合集,git 会把这次操作明确记录为一次重命名。
如果你已经误删了文件并新建,别慌,git 有 --find-renames 机制。提交时使用:
git add -a git commit # 查看提交摘要时如果没识别到 rename,可以: git diff-tree -m --summary head
只要内容相似度足够高,git 依然可能识别为 rename。不过最好的办法还是从一开始就用 git mv 或者在提交前用 mv 保留文件内容。
5.5 文件名开头带横杠的文件怎么修改?
你可能会遇到这样一个文件: -test.txt 。直接执行 mv -test.txt test.txt ,系统会把它当成命令参数,常见的报错是 mv: 无效选项 -- 't' 。
解决方案有两种:
# 方案一:用 -- 结束参数解析,之后的字符串都当作文件名处理 mv -- -test.txt test.txt # 方案二:用 ./ 指定当前路径 mv ./-test.txt test.txt
第二种方案更通用,因为它不依赖单个命令是否支持 -- 。包括 rename 也可以这样处理,虽然批量处理这种文件名的场景不多见,但万一遇到了至少不懵。
5.6 中文字符名文件在两个系统间拷贝后名称截断
这个和文件系统有关。fat32 格式的 u 盘单个文件名最长支持 255 字节(utf-8 编码下一个中文占 3 字节,所以大约 85 个中文字符),某些 smb 挂载的共享目录也有类似的长度限制。如果从 linux 往这些文件系统拷文件,超长文件名会被截断,中文可能直接变乱码。
处理思路:拷贝前先批量缩短文件名,把不必要的修饰词去掉。可以用 rename 把多余部分删掉:
# 把文件名中的 "(最终版)" 删掉,注意系统中括号的转义 rename 's/(最终版)//' *.pdf
这类问题其实防不胜防,最有效的办法还是提前做长度检查,写一个统计脚本把超长文件列出来再逐一处理。
实操总结与个人建议
看完全文你会发现,linux 修改文件名这件事,真正核心的并不是简单的"记住哪个命令",而是理解文件名的本质是 路径中的一个组成部分 ,改名就是改路径。有了这个认知,你就能明白为什么 mv 可以当改名用,为什么 find 能定位到某个层级再配合 mv 改名,也自然理解批量处理的脚本该怎么写。
我个人在实操中最常用的组合是:单个文件用 mv -i ,批量替换统一规则用 perl 版 rename ,复杂跨目录场景用 find 加 while 循环。在这之外,始终保留一份操作前的文件清单和可回滚脚本。真遇到后悔的情况,至少能照着清单把名字改回来。
最后再分享一个实用的小技巧:如果你经常要批量改文件名,与其每次现写命令,不如把这些场景固化成一个 shell 函数放进 ~/.bashrc 里。比如我常用一个函数,把当前目录下所有包含空格的图片文件改成下划线命名:
rename_spaces() {
rename 's/ /_/g' *.{jpg,jpeg,png,gif,bmp} 2>/dev/null || \
for file in *\ *.{jpg,jpeg,png,gif,bmp}; do mv "$file" "${file// /_}"; done
}
这样在脑海里的记忆负担就小很多。多整理几个属于自己的小工具函数,linux 命令行的效率才会真正上一个台阶。
以上就是从mv到rename详解linux中修改文件名的主流方法的详细内容,更多关于linux修改文件名的资料请关注代码网其它相关文章!
发表评论