当前位置: 代码网 > 服务器>服务器>Linux > 从mv到rename详解Linux中修改文件名的主流方法

从mv到rename详解Linux中修改文件名的主流方法

2026年09月20日 Linux 我要评论
用惯了鼠标在图形界面里右键改名,一进到纯命令行的 linux 环境,不少人是真不习惯。最直接的表现就是:想改个文件名,第一反应是 mv ,但用完又总觉得这不就是"移动"文件吗,

用惯了鼠标在图形界面里右键改名,一进到纯命令行的 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

这行命令做了三件事:

  1. find /data/logs -type f -name "*.log" 找出所有后缀是 .log 的文件;
  2. 通过管道 | 把这些文件列表逐行传给 while read 循环;
  3. 循环里用 ${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 正则表达式的语法。

解决方案有两条路:

  1. 改用 c 版的语法: rename .txt .md *.txt
  2. 安装 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修改文件名的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com