是否有一些工具可以在一个目录中查找文件而不是在另一个目录中查找文件?

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 也可以找到两个目录内容之间的差异。它也能解决同样的任务吗?

或者其他什么工具?

谢谢。

Mal*_*ppa 9

是的,您可以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 的解决方案。


Sté*_*las 5

如果您的文件名不包含换行符,您可以执行以下操作:

(export LC_ALL=C; comm -23 <(ls -A dir1) <(ls -A dir2))
Run Code Online (Sandbox Code Playgroud)

找出dir1dir2.

对于任意文件名,您可以使用以下数组减法功能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,看看我是如何得到的,但我也可以得到),这些字母是相同的, sodir1dir2似乎包含相同的文件。

而在C语言环境,commls查看每个那些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存在或者是(当前用户在该路径上已损坏/当前无法解析)符号链接。

  • @MalteSkoruppa 管道传输时,默认情况下,`ls` 的结果每行列出 1 个。`man ls` 不会告诉你,但 `info ls` 会以某种方式告诉你。 (2认同)

归档时间:

查看次数:

1179 次

最近记录:

4 年,10 月 前