Tim*_*Tim 2 directory diff files
我想编写一个 bash 脚本来在一个目录中而不是在另一个目录中查找这些文件。
下面的脚本有效吗?什么时候不行?
for i in "$1"/*; do
f=$(basename $i);
if [ ! -e "$2"/"$f" ]
then
echo $f
fi
done
Run Code Online (Sandbox Code Playgroud)
我听说diff
也可以找到两个目录内容之间的差异。它也能解决同样的任务吗?
或者其他什么工具?
谢谢。
是的,您可以diff
为此目的使用。这很简单:
diff -rq dir1 dir2
Run Code Online (Sandbox Code Playgroud)
该-r
选项也告诉diff
递归到子目录。该-q
选项告诉diff
仅在文件不同时报告。
当我想找出哪些文件在 中dir1
而不是在 中时dir2
,我通常使用这两个选项,反之亦然。(-r
如果不想递归进入子目录,也可以去掉该参数,只考虑两个目录的直接内容。)
请注意,这将显示存在于 中dir1
但不存在于中的dir2
文件,以及存在于dir2
中但不存在于 中的文件dir1
,例如:
$ diff -rq /tmp/dir1/ /tmp/dir2/
Only in /tmp/dir1/: file1
Only in /tmp/dir2/: file2
Only in /tmp/dir2/: file3
Run Code Online (Sandbox Code Playgroud)
如果您只需要一个方向(例如,在 中dir1
但不在 中dir2
的文件)并且仅获取文件名列表(没有“仅在...”混乱),您当然可以尝试使用 按摩diff
的输出grep
, sed
,awk
等,但在这种情况下,您最好不要diff
首先使用,而是使用 Stéphane Chazelas 的解决方案。
如果您的文件名不包含换行符,您可以执行以下操作:
(export LC_ALL=C; comm -23 <(ls -A dir1) <(ls -A dir2))
Run Code Online (Sandbox Code Playgroud)
找出dir1
在dir2
.
对于任意文件名,您可以使用以下数组减法功能zsh
:
dir1_files=(dir1/*(DN:t)) dir2_files=(dir2/*(DN:t))
dir1_and_not_dir2_files=(${dir1_files:|dir2_files})
Run Code Online (Sandbox Code Playgroud)
(其他城市*
到**/*
的文件的递归列表)
或者使用 bash4.4+ 和最新版本的 GNU 实用程序:
readarray -td '' dir1_and_not_dir2_files < <(
export LC_ALL=C
shopt -s nullglob dotglob
comm -z23 <(printf '%s\0' dir1/* | cut -zd/ -f2-) \
<(printf '%s\0' dir2/* | cut -zd/ -f2-)
)
Run Code Online (Sandbox Code Playgroud)
(使用globstar
选件和替换*
用**
一个递归的列表)。
将LC_ALL=C
需要至少有两个方面的原因:
文件名可以包含任何字节序列(除了 0 或/
(基于 ASCII 的系统上的 0x2F)的值),而它comm
是一个文本实用程序,因此对于那些未格式化有效字符的字节序列,其行为未指定。在 C 语言环境中,所有字符都是单字节,所有字节都是有效字符(尽管可能未定义),sp 任何文件名都是有效文本(也考虑到最大文件名长度通常明显小于最大文本行长度) .
更重要的是,comm
需要排序的输入,但在某些语言环境中,某些字符具有未定义的排序顺序或与其他字符的排序相同,这会混淆comm
. 例如,在 en_GB.UTF-8 语言环境中的 GNU 系统上:
$ ls dir1 dir2
dir1:
dir2:
$ locale
LANG=en_GB.UTF-8
LC_CTYPE="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
[...]
$ comm -23 <(ls -A dir1) <(ls -A dir2)
$ (export LC_ALL=C; comm -23 <(ls -A dir1) <(ls -A dir2))
Run Code Online (Sandbox Code Playgroud)
这些是数学字母,其顺序未在 GNU 语言环境中定义,因此在en_GB.UTF-8
语言环境中,comm
就相关而言(或由 完成的排序ls
,看看我是如何得到的,但我也可以得到),这些字母是相同的, sodir1
和dir2
似乎包含相同的文件。
而在C语言环境,comm
并ls
查看每个那些UTF-8字符的编码和分拣的字节中的一个字符是基于字节的值,因此,所有的文件名是不同的(除了实际上被视为具有四个未定义字符字节值 0xf0 0x9d 0x90 0x82,在两个目录中都可以看到)。
除了以换行符结尾的文件名外,您的方法,basename $i
固定为basename -- "$i"
和echo $f
固定为printf '%s\n' "$f"
可以工作,但语义上存在细微差别:
Glob 扩展为目录条目。shell 不需要对目录进行搜索访问来扩展其中的 glob。
"$1"/*
将扩展到所有"$1"
不以.
(隐藏的)开头的目录条目。
虽然它[ -e "dir2/$f" ]
根本不关心目录条目(即使您没有读取权限,dir2
只要您有搜索权限,它也会成功),但它会尝试stat()
对该文件进行系统调用。如果您对dir2
目标不存在或无法访问的符号链接文件没有搜索权限,您将看到不同之处。例如:
ln -s /var/spool/cron/crontab/root dir2/root-crontab
[ -e dir2/root-crontab ]
Run Code Online (Sandbox Code Playgroud)
如果仅以用户身份运行(因为您没有对 的读访问权限/var/spool/cron/crontab
),则将返回 false,但如果以 root 身份运行(如果root
有 crontab ),则可能返回 true 。
也可以看看:
[ -e "dir2/$f" ] || [ -L "$dir2/$f" ]
Run Code Online (Sandbox Code Playgroud)
测试是否dir2/$f
存在或者是(当前用户在该路径上已损坏/当前无法解析)符号链接。
归档时间: |
|
查看次数: |
1179 次 |
最近记录: |