如何在 Linux 上将所有文件夹和文件重命名为小写?
Posted
技术标签:
【中文标题】如何在 Linux 上将所有文件夹和文件重命名为小写?【英文标题】:How do I rename all folders and files to lowercase on Linux? 【发布时间】:2010-09-14 05:30:29 【问题描述】:我必须递归地重命名一个完整的文件夹树,这样任何地方都不会出现大写字母(它是 C++ 源代码,但这不重要)。
忽略 CVS 和 Subversion 版本控制文件/文件夹的奖励积分。首选方式是 shell 脚本,因为任何 Linux 机器上都应该有 shell。
关于文件重命名的细节有一些有效的论据。
我认为应该覆盖具有相同小写名称的文件;这是用户的问题。当在忽略大小写的文件系统上签出时,它也会用后者覆盖第一个。
我会考虑 A-Z 字符并将它们转换为 a-z,其他一切都只是在提出问题(至少在源代码方面)。
在 Linux 系统上运行构建需要该脚本,所以我认为应该省略对 CVS 或 Subversion 版本控制文件的更改。毕竟,这只是一个临时结账。也许“出口”更合适。
【问题讨论】:
之前发布的内容可以开箱即用,也可以针对简单情况进行一些调整,但是在运行批量重命名之前,您可能需要考虑一些情况: 1. 应该发生什么如果您在路径层次结构中有两个或多个相同级别的名称,它们仅大小写不同,例如ABCdef
、abcDEF
和aBcDeF
?重命名脚本应该中止还是只是警告并继续? 2. 如何为非 US-ASCII 名称定义小写字母?如果可能存在这样的名称,是否应该首先执行一次检查和排除通过? 3.如果您正在运行重命名操作
【参考方案1】:
我还是很喜欢小一点的:
rename 'y/A-Z/a-z/' *
在 OS X 的 HFS+ 等不区分大小写的文件系统上,您需要添加 -f
标志:
rename -f 'y/A-Z/a-z/' *
【讨论】:
linux.icydog.net/rename.php:The renaming utility that comes by default with Ubuntu is a Perl program sometimes called prename
这假设您有 perls 重命名,但并非总是如此。例如我的 debian 重命名完全不同
在Arch上,程序叫perl-rename
(由同名包提供)
这不能回答给定的问题,因为“重命名”不是递归的。
在 OSX 上确保您已重命名。 brew install rename
【参考方案2】:
使用"rename"
命令的简洁版本:
find my_root_dir -depth -exec rename 's/(.*)\/([^\/]*)/$1\/\L$2/' \;
这避免了在文件之前重命名目录和尝试将文件移动到不存在的目录(例如 "A/A"
到 "a/a"
)的问题。
或者,不使用"rename"
的更详细的版本。
for SRC in `find my_root_dir -depth`
do
DST=`dirname "$SRC"`/`basename "$SRC" | tr '[A-Z]' '[a-z]'`
if [ "$SRC" != "$DST" ]
then
[ ! -e "$DST" ] && mv -T "$SRC" "$DST" || echo "$SRC was not renamed"
fi
done
附言
后者允许更灵活地使用移动命令(例如,"svn mv"
)。
【讨论】:
使用 rename 也可以这样做 rename 'y/A-Z/a-z/' * 当心,这两个版本都不能在不区分大小写的文件系统上工作。就我而言,我将then
-enclosed 行替换为(mv "$SRC" "$DST.renametmp" && mv "$DST.renametmp" "$DST") || echo "$SRC was not renamed"
。
对于包含空格的文件(上下文:linux、bash),第二种方法对我来说无法正常工作。
使用最新版本的重命名,不需要正则表达式。完整的命令变为find my_root_dir -depth -exec rename -c \;
。如果您在不区分大小写的文件系统(例如 Mac)上,则添加 -f 以重命名
第二个在 Solaris 上为我工作,方法是从 mv
命令中删除 -T
。【参考方案3】:
for f in `find`; do mv -v "$f" "`echo $f | tr '[A-Z]' '[a-z]'`"; done
【讨论】:
请在运行脚本之前执行“mkdir -p A/B/C”。 "find" 应该替换为您用来获取要重命名的文件列表的任何命令;例如,“ls *.C,CPP,H”。 在 OS X 上不起作用,只打印find
的使用信息。 find .
似乎解决了这个问题。
来自所有 cmets 的“合并”版本(无法再编辑答案):for f in `find .`; do mv -v "$f" "`echo $f | tr '[A-Z]' '[a-z]'`"; done
有人可以向我解释为什么这首先应该起作用吗?如果它找到 ./FOO 和 ./FOO/BAR,它首先将 ./FOO 重命名为 ./foo,之后它就再也找不到 ./FOO/BAR。在下面输入我自己的答案应该可以解决这个问题。【参考方案4】:
如果您不需要关心效率,只需尝试以下操作即可。
zip -r foo.zip foo/*
unzip -LL foo.zip
【讨论】:
哇,这种方法效率很低。 假设当您发现自己需要重命名目录中的文件时,计算效率通常不是问题......我认为这可能是这个线程上最具创造性的简单解决方案。确实,这种微不足道的事情不应该像公认的答案所表明的那样复杂,而且这种解决方法与我想在这样的操作中投入的思想量成比例得多。 +1000 您可以使用-0
(即“零”,无压缩)开关,zip -0 -r foo.zip foo/
加速它。
我不得不在不同的目录结构中更改超过 50'000 个文件名。最初接受的答案在 2 分钟内只完成了 10% 的工作,而 -0
压缩几乎是即时的!
这不仅仅是低效。如果没有足够的可用空间用于临时存档,则此操作简直是不可能。【参考方案5】:
可以简单地使用以下不太复杂的:
rename 'y/A-Z/a-z/' *
【讨论】:
这不起作用,它说小写名称的文件已经存在。 @kirhgoff 我已经看到 NFS 挂载会发生这种情况——按照说明进行操作 here。 我们如何在区分大小写的文件系统上重命名?rename 'y/a-z/A-Z/' /tmp/*.jpg
-> Can't rename /tmp/*.jpg /TMP/*.JPG: No such file or directory
,即尝试重命名整个路径而不仅仅是文件。
这不会向this identical answer posted 6 years earlier 添加任何内容。【参考方案6】:
如果您已经拥有或设置了 rename 命令(例如,在 Mac 中通过 brew install),则此方法有效:
rename --lower-case --force somedir/*
【讨论】:
... 如果您先设置重命名,例如通过brew install rename
重命名 'y/A-Z/a-z/' *
--lower-case
?遗憾的是,至少在 Arch Linux 中并非如此。
rename
不是内置的 bash。它是一个外部实用程序,语法会因您安装的版本而异。正如声称的那样,这个答案不足以作为“适用于所有基于 unix 的操作系统”的便携式解决方案。
刚查了一下,最新的Fedora(28)中包含的rename
命令,通过util-linux-2.31
包,没有--lower-case
选项。【参考方案7】:
这适用于CentOS/Red Hat Linux 或其他没有rename
Perl 脚本的发行版:
for i in $( ls | grep [A-Z] ); do mv -i "$i" "`echo $i | tr 'A-Z' 'a-z'`"; done
来源:Rename all file names from uppercase to lowercase characters
(在某些发行版中,默认的 rename
命令来自 util-linux,这是一个不同的、不兼容的工具。)
【讨论】:
这只适用于单个目录,不能递归 酷.. 效果很好【参考方案8】:上面的大多数答案都很危险,因为它们不处理包含奇数字符的名称。对于这种事情,您最安全的选择是使用find
的-print0
选项,它将以ASCII NUL 而不是\n 来终止文件名。
这是一个脚本,它只改变文件而不是目录名,以免混淆find
:
find . -type f -print0 | xargs -0n 1 bash -c \
's=$(dirname "$0")/$(basename "$0");
d=$(dirname "$0")/$(basename "$0"|tr "[A-Z]" "[a-z]"); mv -f "$s" "$d"'
我对其进行了测试,它适用于包含空格、各种引号等的文件名。这很重要,因为如果您以 root 身份在包含由创建的文件的树上运行其他脚本之一
touch \;\ echo\ hacker::0:0:hacker:\$\'\057\'root:\$\'\057\'bin\$\'\057\'bash
...你猜怎么着...
【讨论】:
使用rename
而不是mv
会更简单。【参考方案9】:
我在 Mac OS X 上找到的最简单的方法是使用来自 http://plasmasturm.org/code/rename/ 的重命名包:
brew install rename
rename --force --lower-case --nows *
--force Rename 即使目标名称的文件已经存在。
--lower-case 将文件名全部转换为小写。
--nows 用单个下划线字符替换文件名中的所有空格序列。
【讨论】:
【参考方案10】:这是我的次优解决方案,使用 Bash shell 脚本:
#!/bin/bash
# First, rename all folders
for f in `find . -depth ! -name CVS -type d`; do
g=`dirname "$f"`/`basename "$f" | tr '[A-Z]' '[a-z]'`
if [ "xxx$f" != "xxx$g" ]; then
echo "Renaming folder $f"
mv -f "$f" "$g"
fi
done
# Now, rename all files
for f in `find . ! -type d`; do
g=`dirname "$f"`/`basename "$f" | tr '[A-Z]' '[a-z]'`
if [ "xxx$f" != "xxx$g" ]; then
echo "Renaming file $f"
mv -f "$f" "$g"
fi
done
文件夹都已正确重命名,mv
不会在权限不匹配时询问问题,并且 CVS 文件夹未重命名(不幸的是,该文件夹内的 CVS 控制文件仍被重命名)。
由于“find -depth”和“find | sort -r”都以可用的重命名顺序返回文件夹列表,因此我更喜欢使用“-depth”来搜索文件夹。
【讨论】:
您的第一个查找不起作用。尝试“mkdir -p A/B/C”,然后运行你的脚本。【参考方案11】:单线:
for F in K*; do NEWNAME=$(echo "$F" | tr '[:upper:]' '[:lower:]'); mv "$F" "$NEWNAME"; done
请注意,这只会转换以字母 K 开头的文件/目录,因此请进行相应调整。
【讨论】:
【参考方案12】:最初的问题要求忽略 SVN 和 CVS 目录,这可以通过在 find 命令中添加 -prune 来完成。例如忽略 CVS:
find . -name CVS -prune -o -exec mv '' `echo | tr '[A-Z]' '[a-z]'` \; -print
[edit] 我试过了,在 find 中嵌入小写翻译不起作用,原因我不明白。因此,将其修改为:
$> cat > tolower
#!/bin/bash
mv $1 `echo $1 | tr '[:upper:]' '[:lower:]'`
^D
$> chmod u+x tolower
$> find . -name CVS -prune -o -exec tolower '' \;
伊恩
【讨论】:
【参考方案13】:不可移植,仅限 Zsh,但非常简洁。
首先,确保 zmv
已加载。
autoload -U zmv
另外,请确保 extendedglob
已开启:
setopt extendedglob
然后使用:
zmv '(**/)(*)~CVS~**/CVS' '$1$(L)2'
递归小写文件和名称不是CVS的目录。
【讨论】:
【参考方案14】:使用 Larry Wall 的文件名修复程序:
$op = shift or die $help;
chomp(@ARGV = <STDIN>) unless @ARGV;
for (@ARGV)
$was = $_;
eval $op;
die $@ if $@;
rename($was,$_) unless $was eq $_;
就这么简单
find | fix 'tr/A-Z/a-z/'
(其中fix
当然是上面的脚本)
【讨论】:
【参考方案15】:这是一个执行您要求的小型 shell 脚本:
root_directory="$1?-please specify parent directory"
do_it ()
awk ' lc= tolower($0); if (lc != $0) print "mv \"" $0 "\" \"" lc "\"" ' | sh
# first the folders
find "$root_directory" -depth -type d | do_it
find "$root_directory" ! -type d | do_it
注意第一个 find 中的 -depth 动作。
【讨论】:
唯一能在 Mac 上从整个线程中工作的东西。谢谢大家!【参考方案16】:for f in `find -depth`; do mv $f $f,, ; done
find -depth
打印每个文件和目录,并在目录本身之前打印目录的内容。 $f,,
将文件名小写。
【讨论】:
这不起作用:它在对内容进行操作之前重命名目录,这样以后对内容的尝试就会失败。 添加-depth
解决了这个问题!这是一个非常快速的解决方案,但当然不使用-print0
选项查找,它不是最可靠的
@jpaugh 不,它没有。它将尝试将 ./FOO/BAR 重命名为 ./foo/bar,但此时 ./foo 尚不存在。【参考方案17】:
使用typeset:
typeset -l new # Always lowercase
find $topPoint | # Not using xargs to make this more readable
while read old
do new="$old" # $new is a lowercase version of $old
mv "$old" "$new" # Quotes for those annoying embedded spaces
done
在 Windows 上,Git Bash 等模拟可能会失败,因为 Windows 在后台不区分大小写。对于这些,请先将mv
的文件添加到另一个名称,例如“$old.tmp”,然后添加到$new
。
【讨论】:
【参考方案18】:使用 MacOS,
安装rename
包,
brew install rename
使用,
find . -iname "*.py" -type f | xargs -I% rename -c -f "%"
此命令查找所有带有*.py
扩展名的文件并将文件名转换为小写。
`f` - forces a rename
例如,
$ find . -iname "*.py" -type f
./sample/Sample_File.py
./sample_file.py
$ find . -iname "*.py" -type f | xargs -I% rename -c -f "%"
$ find . -iname "*.py" -type f
./sample/sample_file.py
./sample_file.py
【讨论】:
你为什么在这里为xargs
使用-I
选项?
- I
标志不是强制性的。当我们使用xargs
执行多个命令时使用它。这是一个使用多个命令的示例 xargs
cat foo.txt | xargs -I % sh -c 'echo %; mkdir %'
您可以在没有 -I
的情况下运行,例如 find . -iname "*.py" -type f | xargs -I% rename -c -f "%"
【参考方案19】:
冗长但“没有惊喜也没有安装”
此脚本处理带有空格、引号、其他不常见字符和 Unicode 的文件名,适用于不区分大小写的文件系统和大多数已安装 bash 和 awk 的 Unix-y 环境(即几乎所有)。如果有任何冲突,它还会报告冲突(将文件名保留为大写),当然还会重命名文件和目录并递归工作。最后,它具有很强的适应性:您可以调整 find 命令以定位您希望的文件/目录,并且可以调整 awk 以进行其他名称操作。请注意,“处理 Unicode”是指它确实会转换它们的大小写(而不是像使用 tr
的答案一样忽略它们)。
# adapt the following command _IF_ you want to deal with specific files/dirs
find . -depth -mindepth 1 -exec bash -c '
for file do
# adapt the awk command if you wish to rename to something other than lowercase
newname=$(dirname "$file")/$(basename "$file" | awk "print tolower(\$0)")
if [ "$file" != "$newname" ] ; then
# the extra step with the temp filename is for case-insensitive filesystems
if [ ! -e "$newname" ] && [ ! -e "$newname.lcrnm.tmp" ] ; then
mv -T "$file" "$newname.lcrnm.tmp" && mv -T "$newname.lcrnm.tmp" "$newname"
else
echo "ERROR: Name already exists: $newname"
fi
fi
done
' sh +
参考文献
我的脚本基于这些出色的答案:
https://unix.stackexchange.com/questions/9496/looping-through-files-with-spaces-in-the-names
How to convert a string to lower case in Bash?
【讨论】:
【参考方案20】:这也适用于 macOS:
ruby -e "Dir['*'].each |p| File.rename(p, p.downcase) "
【讨论】:
这特别好,因为 Ruby 随 macOS 一起提供。【参考方案21】:在 OS X 中,mv -f
显示“相同文件”错误,所以我重命名了两次:
for i in `find . -name "*" -type f |grep -e "[A-Z]"`; do j=`echo $i | tr '[A-Z]' '[a-z]' | sed s/\-1$//`; mv $i $i-1; mv $i-1 $j; done
【讨论】:
【参考方案22】:我需要在 Windows 7 上的 Cygwin 设置上执行此操作,并发现我在上面尝试过的建议中遇到了语法错误(尽管我可能错过了一个工作选项)。然而,这个直接来自Ubuntu forums 的解决方案是可行的:-)
ls | while read upName; do loName=`echo "$upName" | tr '[:upper:]' '[:lower:]'`; mv "$upName" "$loName"; done
(注意:我之前用下划线替换了空格:
for f in *\ *; do mv "$f" "$f// /_"; done
)
【讨论】:
【参考方案23】:Slugify 重命名(正则表达式)
这不是 OP 要求的,而是我希望在此页面上找到的:
用于重命名文件的“slugify”版本,使其类似于 URL(即仅包括字母数字、点和破折号):
rename "s/[^a-zA-Z0-9\.]+/-/g" filename
【讨论】:
“不完全是”?这根本不是 OP 要求的【参考方案24】:在这种情况下,我会使用 Python,以避免乐观地假设没有空格或斜杠的路径。我还发现python2
的安装位置往往比rename
更多。
#!/usr/bin/env python2
import sys, os
def rename_dir(directory):
print('DEBUG: rename('+directory+')')
# Rename current directory if needed
os.rename(directory, directory.lower())
directory = directory.lower()
# Rename children
for fn in os.listdir(directory):
path = os.path.join(directory, fn)
os.rename(path, path.lower())
path = path.lower()
# Rename children within, if this child is a directory
if os.path.isdir(path):
rename_dir(path)
# Run program, using the first argument passed to this Python script as the name of the folder
rename_dir(sys.argv[1])
【讨论】:
谈论让它变得复杂......只是做......重命名'y/A-Z/a-z/'* @Stephen,假设您具有安装重命名的管理员权限。我经常需要在无法安装其他软件的系统上处理数据。在最近的 Ubuntu 中,只有当 perl 存在时才会安装它。【参考方案25】:如果您使用Arch Linux,您可以从AUR
安装rename
) 包,该包提供renamexm
命令作为/usr/bin/renamexm
可执行文件和a manual page 连同它。
它是一个非常强大的工具,可以快速重命名文件和目录。
转为小写
rename -l Developers.mp3 # or --lowcase
转换为大写
rename -u developers.mp3 # or --upcase, long option
其他选项
-R --recursive # directory and its children
-t --test # Dry run, output but don't rename
-o --owner # Change file owner as well to user specified
-v --verbose # Output what file is renamed and its new name
-s/str/str2 # Substitute string on pattern
--yes # Confirm all actions
如果需要,您可以从 here 获取示例 Developers.mp3 文件;)
【讨论】:
在 macOS 上从brew
重命名,-l
绑定到创建符号链接,-c
转换为小写,所以要小心。【参考方案26】:
( find YOURDIR -type d | sort -r;
find yourdir -type f ) |
grep -v /CVS | grep -v /SVN |
while read f; do mv -v $f `echo $f | tr '[A-Z]' '[a-z]'`; done
首先重命名目录自下而上sort -r(其中-depth 不可用),然后是文件。 然后 grep -v /CVS 而不是 find ...-prune 因为它更简单。 对于大型目录,for f in ... 可能会溢出一些 shell 缓冲区。 使用 find ... |同时阅读以避免这种情况。
是的,这将破坏仅在情况下有所不同的文件......
【讨论】:
第一:find YOURDIR -type d | sort -r
太麻烦了。你想要find YOURDIR -depth -type d
。其次,find -type f
必须在重命名目录后运行。【参考方案27】:
find . -depth -name '*[A-Z]*'|sed -n 's/\(.*\/\)\(.*\)/mv -n -v -T \1\2 \1\L\2/p'|sh
我没有尝试过这里提到的更复杂的脚本,但没有一个命令行版本适用于我的 Synology NAS。 rename
不可用,find
的许多变体都失败了,因为它似乎坚持已重命名路径的旧名称(例如,如果它找到 ./FOO
后跟 ./FOO/BAR
,则重命名 ./FOO
到./foo
仍将继续列出./FOO/BAR
,即使该路径不再有效)。上面的命令对我有用,没有任何问题。
下面是对命令各部分的解释:
find . -depth -name '*[A-Z]*'
这将从当前目录中找到任何文件(将.
更改为您要处理的任何目录),使用深度优先搜索(例如,它将在./foo
之前列出./foo/bar
),但仅对于包含大写字符的文件。 -name
过滤器仅适用于基本文件名,而不适用于完整路径。所以这将列出./FOO/BAR
,而不是./FOO/bar
。这没关系,因为我们不想重命名./FOO/bar
。虽然我们想重命名./FOO
,但稍后会列出这个名称(这就是-depth
很重要的原因)。
这个命令本身对于首先查找要重命名的文件特别有用。在完整的重命名命令之后使用它来搜索由于文件名冲突或错误而仍未被替换的文件。
sed -n 's/\(.*\/\)\(.*\)/mv -n -v -T \1\2 \1\L\2/p'
这部分读取find
输出的文件,并使用正则表达式将它们格式化为mv
命令。 -n
选项阻止 sed
打印输入,并且搜索和替换正则表达式中的 p
命令输出替换的文本。
正则表达式本身包含两个捕获:直到最后一个 / 的部分(这是文件的目录),以及文件名本身。目录保持不变,但文件名被转换为小写。因此,如果find
输出./FOO/BAR
,它将变为mv -n -v -T ./FOO/BAR ./FOO/bar
。 mv
的 -n
选项确保不会覆盖现有的小写文件。 -v
选项使mv
输出它所做的每一个更改(或不进行 - 如果./FOO/bar
已经存在,它会输出类似./FOO/BAR -> ./FOO/BAR
的内容,注意没有进行任何更改)。 -T
在这里非常重要——它将目标文件视为一个目录。这将确保 ./FOO/BAR
在该目录恰好存在时不会移动到 ./FOO/bar
。
将其与find
一起使用以生成将要执行的命令列表(便于验证将要执行的操作而无需实际执行)
sh
这很不言自明。它将所有生成的mv
命令路由到shell 解释器。您可以将其替换为 bash
或您喜欢的任何 shell。
【讨论】:
【参考方案28】:这里没有一个解决方案对我有用,因为我所在的系统无法访问 perl 重命名脚本,而且一些文件包含空格。但是,我发现了一个可行的变体:
find . -depth -exec sh -c '
t=$0%/*/$(printf %s "$0##*/" | tr "[:upper:]" "[:lower:]");
[ "$t" = "$0" ] || mv -i "$0" "$t"
' \;
归功于“Gilles 'SO-stop be evil'”,请参阅 this answer on the similar question "change entire directory tree to lower-case names" on the Unix & Linux StackExchange。
【讨论】:
【参考方案29】:我相信单行可以简化:
for f in **/*; do mv "$f" "$f:l"; done
【讨论】:
以上是关于如何在 Linux 上将所有文件夹和文件重命名为小写?的主要内容,如果未能解决你的问题,请参考以下文章
如何在Linux中将所有包含下划线的文件和文件夹重命名为连字符
在 Windows 7 上将文件重命名为 md5 sum + 扩展名(使用 CMD 或 PowerShell 2013)