如何返回同名但大小写不同的文件列表,这些文件位于同一目录中?
我不关心文件的内容,只需要知道任何具有相同名称副本的文件的位置和名称。
重复文件示例:
/www/images/taxi.jpg
/www/images/Taxi.jpg
理想情况下,我需要从一个基础目录递归搜索所有文件。在上面的例子中,这个基础目录是/www/
。如何返回同名但大小写不同的文件列表,这些文件位于同一目录中?
我不关心文件的内容,只需要知道任何具有相同名称副本的文件的位置和名称。
重复文件示例:
/www/images/taxi.jpg
/www/images/Taxi.jpg
理想情况下,我需要从一个基础目录递归搜索所有文件。在上面的例子中,这个基础目录是/www/
。perl -pe 's!([^/]+)$!lc $1!e'
这将仅将路径中的文件名部分转换为小写。
编辑1:实际上,整个问题可以通过以下方式解决:
find . | perl -ne 's!([^/]+)$!lc $1!e; print if 1 == $seen{$_}++'
find . |sed 's,\(.*\)/\(.*\)$,\1/\2\t\1/\L\2,'|sort|uniq -D -f 1|cut -f 1
find
所示。不太优雅,但仍有效:#!/usr/bin/perl -w
use strict;
use warnings;
my %dup_series_per_dir;
while (<>) {
my ($dir, $file) = m!(.*/)?([^/]+?)$!;
push @{$dup_series_per_dir{$dir||'./'}{lc $file}}, $file;
}
for my $dir (sort keys %dup_series_per_dir) {
my @all_dup_series_in_dir = grep { @{$_} > 1 } values %{$dup_series_per_dir{$dir}};
for my $one_dup_series (@all_dup_series_in_dir) {
print "$dir\{" . join(',', sort @{$one_dup_series}) . "}\n";
}
}
find . |xargs -n 1 basename| perl -ne 's!([^/]+)$!lc $1!e; print if 1 == $seen{$_}++'
- Peterfind . | perl -ne 's!.*/([^/]+)!lc $1!se; print if 1 == $seen{$_}++'
。 - Christoffer Hammarström尝试:
ls -1 | tr '[A-Z]' '[a-z]' | sort | uniq -c | grep -v " 1 "
简单明了:)管道真是太神奇了!
ls -1
命令可以让文件每行一个显示,tr '[A-Z]' '[a-z]'
命令将所有大写字母转换为小写字母,sort
命令按顺序排列它们(出人意料吧),uniq -c
命令删除重复行并同时给您计数,最后,grep -v " 1 "
命令剥离掉出现次数为1的行。
当我在含有一个"重复"文件的目录中运行此命令时(我将"qq"复制到"qQ"中),我得到:
2 qq
对于“当前目录及其所有子目录”版本,只需将 ls -1
替换为 find .
或 find DIRNAME
(如果您想要指定目录作为起始点,则使用 DIRNAME
作为您要使用的目录名称)。
这会返回以下结果(适用于我):
2 ./.gconf/system/gstreamer/0.10/audio/profiles/mp3
2 ./.gconf/system/gstreamer/0.10/audio/profiles/mp3/%gconf.xml
2 ./.gnome2/accels/blackjack
2 ./qq
这些原因包括:
pax> ls -1d .gnome2/accels/[bB]* .gconf/system/gstreamer/0.10/audio/profiles/[mM]* [qQ]?
.gconf/system/gstreamer/0.10/audio/profiles/mp3
.gconf/system/gstreamer/0.10/audio/profiles/MP3
.gnome2/accels/blackjack
.gnome2/accels/Blackjack
qq
qQ
更新:
事实上,在进一步思考后,tr
将会将路径的所有组件转换为小写字母,因此以下两种写法会被视为相同:
/a/b/c
/a/B/c
即使它们在不同的目录中,也将被视为重复。
如果您只想在单个目录内显示重复项作为匹配项,则可以使用(相当庞大的):
perl -ne '
chomp;
@flds = split (/\//);
$lstf = $f[-1];
$lstf =~ tr/A-Z/a-z/;
for ($i =0; $i ne $#flds; $i++) {
print "$f[$i]/";
};
print "$x\n";'
替代方式:
tr '[A-Z]' '[a-z]'
它所做的只是将路径名的最后一部分转换为小写,而不是整个路径名。此外,如果您只想要普通文件(没有目录、FIFO等),请使用find -type f
来限制返回的内容。
grep -v "^ *1"
。 - Dennis Williamsonfind
命令,但似乎目录路径导致重复项无法被正确找到,因此我在命令中添加了另一个管道,使用awk
从找到的记录中删除目录路径,所以我的最终命令看起来是这样的:find . -type f | awk -F/ '{print $NF}' | tr '[A-Z]' '[a-z]' | sort | uniq -c | grep -v " 1 "
请注意,这只会列出重复的文件名,而不是它们的目录,并且您将无法使用管道来删除它们等。 - SimonDowdles我相信
ls | sort -f | uniq -i -d
export LC_ALL='C'
设置字符编码。 - Turadg针对mpez0的回复,如果要进行递归检测,只需将“ls”替换为“find .”即可。
我唯一看到的问题是,如果这是一个正在复制的目录,则每个文件在此目录中都有1个条目。需要人工处理输出结果。
但无论如何,你不会自动删除这些文件,对吧?
find . | sort -f | uniq -i -d
findsn
,您可以通过编译fslint
获得,而deb包中没有包含它。/findsn --help
find (files) with duplicate or conflicting names.
Usage: findsn [-A -c -C] [[-r] [-f] paths(s) ...]
-A reports all aliases (soft and hard links) to files.
If no path(s) specified then the $PATH is searched.
如果只指定了路径,则会检查是否存在重名文件。您可以使用-C来忽略此搜索中的大小写。使用-c进行限定更加严格,因为仅报告名称仅在大小写上不同的同一目录中的文件(或目录)。即-c将标记文件和目录,如果传输到不区分大小写的文件系统中将产生冲突。请注意,如果指定了-c或-C并且未指定路径,则假定当前目录。
以下是如何查找所有重复的jar文件的示例:
find . -type f -printf "%f\n" -name "*.jar" | sort -f | uniq -i -d
将*.jar
替换为您要查找的任何重复文件类型。
您可以使用:
find -type f -exec readlink -m {} \; | gawk 'BEGIN{FS="/";OFS="/"}{$NF=tolower($NF);print}' | uniq -c
何时使用:
find -type f
递归打印所有文件的完整路径。
-exec readlink -m {} \;
获取文件的绝对路径。
gawk 'BEGIN{FS="/";OFS="/"}{$NF=tolower($NF);print}'
将所有文件名替换为小写字母。
uniq -c
去重路径,-c 输出重复计数。
这是我使用过的一个脚本(我不是作者)。原始脚本和讨论可以在这里找到: http://www.daemonforums.org/showthread.php?t=4661
#! /bin/sh
# find duplicated files in directory tree
# comparing by file NAME, SIZE or MD5 checksum
# --------------------------------------------
# LICENSE(s): BSD / CDDL
# --------------------------------------------
# vermaden [AT] interia [DOT] pl
# http://strony.toya.net.pl/~vermaden/links.htm
__usage() {
echo "usage: $( basename ${0} ) OPTION DIRECTORY"
echo " OPTIONS: -n check by name (fast)"
echo " -s check by size (medium)"
echo " -m check by md5 (slow)"
echo " -N same as '-n' but with delete instructions printed"
echo " -S same as '-s' but with delete instructions printed"
echo " -M same as '-m' but with delete instructions printed"
echo " EXAMPLE: $( basename ${0} ) -s /mnt"
exit 1
}
__prefix() {
case $( id -u ) in
(0) PREFIX="rm -rf" ;;
(*) case $( uname ) in
(SunOS) PREFIX="pfexec rm -rf" ;;
(*) PREFIX="sudo rm -rf" ;;
esac
;;
esac
}
__crossplatform() {
case $( uname ) in
(FreeBSD)
MD5="md5 -r"
STAT="stat -f %z"
;;
(Linux)
MD5="md5sum"
STAT="stat -c %s"
;;
(SunOS)
echo "INFO: supported systems: FreeBSD Linux"
echo
echo "Porting to Solaris/OpenSolaris"
echo " -- provide values for MD5/STAT in '$( basename ${0} ):__crossplatform()'"
echo " -- use digest(1) instead for md5 sum calculation"
echo " $ digest -a md5 file"
echo " -- pfexec(1) is already used in '$( basename ${0} ):__prefix()'"
echo
exit 1
(*)
echo "INFO: supported systems: FreeBSD Linux"
exit 1
;;
esac
}
__md5() {
__crossplatform
:> ${DUPLICATES_FILE}
DATA=$( find "${1}" -type f -exec ${MD5} {} ';' | sort -n )
echo "${DATA}" \
| awk '{print $1}' \
| uniq -c \
| while read LINE
do
COUNT=$( echo ${LINE} | awk '{print $1}' )
[ ${COUNT} -eq 1 ] && continue
SUM=$( echo ${LINE} | awk '{print $2}' )
echo "${DATA}" | grep ${SUM} >> ${DUPLICATES_FILE}
done
echo "${DATA}" \
| awk '{print $1}' \
| sort -n \
| uniq -c \
| while read LINE
do
COUNT=$( echo ${LINE} | awk '{print $1}' )
[ ${COUNT} -eq 1 ] && continue
SUM=$( echo ${LINE} | awk '{print $2}' )
echo "count: ${COUNT} | md5: ${SUM}"
grep ${SUM} ${DUPLICATES_FILE} \
| cut -d ' ' -f 2-10000 2> /dev/null \
| while read LINE
do
if [ -n "${PREFIX}" ]
then
echo " ${PREFIX} \"${LINE}\""
else
echo " ${LINE}"
fi
done
echo
done
rm -rf ${DUPLICATES_FILE}
}
__size() {
__crossplatform
find "${1}" -type f -exec ${STAT} {} ';' \
| sort -n \
| uniq -c \
| while read LINE
do
COUNT=$( echo ${LINE} | awk '{print $1}' )
[ ${COUNT} -eq 1 ] && continue
SIZE=$( echo ${LINE} | awk '{print $2}' )
SIZE_KB=$( echo ${SIZE} / 1024 | bc )
echo "count: ${COUNT} | size: ${SIZE_KB}KB (${SIZE} bytes)"
if [ -n "${PREFIX}" ]
then
find ${1} -type f -size ${SIZE}c -exec echo " ${PREFIX} \"{}\"" ';'
else
# find ${1} -type f -size ${SIZE}c -exec echo " {} " ';' -exec du -h " {}" ';'
find ${1} -type f -size ${SIZE}c -exec echo " {} " ';'
fi
echo
done
}
__file() {
__crossplatform
find "${1}" -type f \
| xargs -n 1 basename 2> /dev/null \
| tr '[A-Z]' '[a-z]' \
| sort -n \
| uniq -c \
| sort -n -r \
| while read LINE
do
COUNT=$( echo ${LINE} | awk '{print $1}' )
[ ${COUNT} -eq 1 ] && break
FILE=$( echo ${LINE} | cut -d ' ' -f 2-10000 2> /dev/null )
echo "count: ${COUNT} | file: ${FILE}"
FILE=$( echo ${FILE} | sed -e s/'\['/'\\\['/g -e s/'\]'/'\\\]'/g )
if [ -n "${PREFIX}" ]
then
find ${1} -iname "${FILE}" -exec echo " ${PREFIX} \"{}\"" ';'
else
find ${1} -iname "${FILE}" -exec echo " {}" ';'
fi
echo
done
}
# main()
[ ${#} -ne 2 ] && __usage
[ ! -d "${2}" ] && __usage
DUPLICATES_FILE="/tmp/$( basename ${0} )_DUPLICATES_FILE.tmp"
case ${1} in
(-n) __file "${2}" ;;
(-m) __md5 "${2}" ;;
(-s) __size "${2}" ;;
(-N) __prefix; __file "${2}" ;;
(-M) __prefix; __md5 "${2}" ;;
(-S) __prefix; __size "${2}" ;;
(*) __usage ;;
esac
OLD : find "${1}" -type f | xargs -n 1 basename
NEW : find "${1}" -type f -printf "%f\n"
find . -type f | awk -F/ '{print $NF}' | sort -f | uniq -i -d
这里我们使用了:
find
- 查找当前目录下的所有文件awk
- 移除文件名中的文件路径部分sort
- 不区分大小写地排序uniq
- 从通过管道传递的内容中找到重复项(受 @mpez0 的回答和 @SimonDowdles 对 @paxdiablo 回答的评论启发。)
你可以使用GNU awk在给定的目录中检查重复项:
gawk 'BEGINFILE {if ((seen[tolower(FILENAME)]++)) print FILENAME; nextfile}' *
这里使用 BEGINFILE 在读取文件之前执行某些操作。在本例中,它通过一个数组 seen[]
来跟踪出现过的名称,其索引是小写的文件名。
如果一个名称已经出现过,无论大小写如何,它都会将其打印出来。否则,它只会跳到下一个文件。
$ tree
.
├── bye.txt
├── hello.txt
├── helLo.txt
├── yeah.txt
└── YEAH.txt
0 directories, 5 files
$ gawk 'BEGINFILE {if ((a[tolower(FILENAME)]++)) print FILENAME; nextfile}' *
helLo.txt
YEAH.txt