如何检测文件是否隐藏

Jos*_*tes 5 bash files

我已经将这段代码作为一些课程作业的一部分编写,我们必须在不使用 find、du 或 -R 命令的情况下创建递归文件计数器。这似乎有效,但总是将 hiddenfilecounter 返回为 0。是否有任何特殊原因这不起作用。

#!/bin/sh
shopt -s dotglob
directoryCounter=0
fileCounter=0
hiddenDirectoryCounter=0
hiddenFileCounter=0
listAllFiles() 
  {
   local dir=$1
   local file
   local bn=$(basename $dir)


   for file in "$dir"/*; do
     if [[ $bn == .* ]]; then
     let hiddenDirectoryCounter+=1
     listAllFiles "$file"
   elif [[ -f $file &&  "$file" == ^. ]]; then
     ls -l $file
     let hiddenFileCounter+=1
   elif [[ -f $file ]];then
     ls -l $file
     let fileCounter+=1
   elif [[ -d $file ]]; then
     listAllFiles "$file"
     let directoryCounter+=1
        fi
  done
} 
listAllFiles $1
echo File found: $fileCounter
echo Directories found: $directoryCounter
echo Hidden directories found: $hiddenDirectoryCounter
echo Hidden files found: $hiddenFileCounter
Run Code Online (Sandbox Code Playgroud)

ter*_*don 7

这里有一些错误。首先,/bin/sh当您真正想要运行/bin/bash.

现在,检查隐藏文件的脚本部分是

elif [[ -f $file &&  "$file" == ^. ]]; then
Run Code Online (Sandbox Code Playgroud)

如果您将脚本运行为foo.sh /home/foo,则每个脚本都$file将以点开头,/home/foo因此即使隐藏文件也不会以点开头。此外,您不能使用^with ==,它无论如何都匹配开头(请参阅此处)。

无论如何,您的脚本的有效实现将是

#!/usr/bin/env bash
shopt -s dotglob
directoryCounter=0
fileCounter=0
hiddenDirectoryCounter=0
hiddenFileCounter=0
listAllFiles() 
  {
   local dir=$1
   local file
   local bn="$(basename -- "$dir")"


   for file in "$dir"/*; do
       ## Use ..* so you don't count the current dir (.) as hidden
       if [[ $bn == .?* ]]; then
        let hiddenDirectoryCounter+=1
        listAllFiles "$file"
       ## Match only the filename, not the whole path      
       elif [[ -f $file &&  "$(basename "$file")" == .* ]]; then
        let hiddenFileCounter+=1
       elif [[ -f "$file" ]];then
        let fileCounter+=1
       elif [[ -d "$file" ]]; then
        listAllFiles "$file"
        let directoryCounter+=1
       fi
  done
} 
listAllFiles $1
echo File found: $fileCounter
echo Directories found: $directoryCounter
echo Hidden directories found: $hiddenDirectoryCounter
echo Hidden files found: $hiddenFileCounter
Run Code Online (Sandbox Code Playgroud)

但是,这不是一个很好的方法,您不能将多个目录作为输入处理,更重要的是,您只检查是否$bn是隐藏目录,从不$file,这意味着您永远不会找到多个隐藏目录你让它变得不必要地复杂。您可以通过使用globstar. 我会这样写:

#!/usr/bin/env bash
shopt -s dotglob

## globstar causes ** to match all files and directories
## recursively.
shopt -s globstar 

for dir in "$@"; do
    for file in "$dir"/**; do
      if [[ -f $file &&  "$(basename "$file")" == .* ]]; then
         let hiddenFileCounter+=1
      elif [[ -d $file &&  "$(basename "$file")" == .?* ]]; then
         let hiddenDirectoryCounter+=1
      elif [[ -f $file ]] ; then
         let fileCounter+=1
      elif [[ -d $file ]] ; then
         let directoryCounter+=1
      fi
    done
done

echo Files found: $fileCounter
echo Directories found: $directoryCounter
echo Hidden directories found: $hiddenDirectoryCounter
echo Hidden files found: $hiddenFileCounter
Run Code Online (Sandbox Code Playgroud)


Sté*_*las 5

您的代码有几个问题:

  1. 如果您指定/bin/sh为 she-bang,那么您应该使用sh语法编写脚本(sh如果您打算移植到非常旧的系统,则应使用POSIX语法或 Bourne shell 语法)。没有shoptsinsh语法(shoptsbash特定的),也没有[[ ... ]](这ksh也是在zshand 中可用的构造bash),也没有local,也没有let

    基本上,您的脚本仅适用于bash,因此您应该使用#! /bin/bash -she-bang(但是您的脚本将无法在bash未安装的系统上运行),或者将其转换为sh语法。

  2. 在列表上下文中不加引号的变量,例如在命令的参数中(如basename $file)是 split+glob 运算符。你不想在这里这样做。

  3. 将任意数据传递给命令时,您必须使用--将参数与选项分开。否则以 开头的文件名将-被视为一个选项。所以:bn=$(basename -- "$file")

  4. 来自 Microsoft 一词的人存在混淆-d-f常见问题。在 Unix 中,一切(大多数东西)都是一个文件。有许多不同类型的文件:目录、符号链接、常规文件、fifos、套接字、设备和许多其他文件,具体取决于实际的 Unix 变体。

    [[ -d ... ]]如果文件是 directory 类型,[[ -f ... ]]如果文件是regular类型,则返回 true 。其他类型的文件呢?同样在符号链接的情况下,[[ -d ... ]]如果文件是符号链接类型,但最终解析为目录类型的文件,则返回 true 。这意味着,例如,如果有一个符号链接指向/那里,您的脚本将无限循环。

    就好像使用find -L(以前,find -follow)一样,除了find -L能够检测循环。出于同样的原因,除非您的版本是 4.3 或更高版本,否则您不能使用bash's globstar(尽管您可以使用没有相同问题的zshksh93等效的)bash

    那么,您是否要考虑目录与非目录文件?还是仅目录常规文件,而忽略所有其他文件?

    在第一种情况下,要测试目录,它是[ -d "$file" ] && [ ! -L "$file" ],而对于非目录:[ -L "$file" ] || [ ! -d "$file" ]

    在第二种情况下:[ -d "$file" ] && [ ! -L "$file" ][ -f "$file" ] && [ ! -L "$file" ]

  5. 应该注意的是,您计算目录条目的次数多于文件数。例如,如果一个文件显示为两个不同的目录条目(两个硬链接),它将被计算两次。还有两个目录条目您没有计算:...(尽管您可能不想计算它们)。

  6. [命令或[[ ... ]]结构和外壳通配符时,他们无法访问文件(或缺乏许可或因为该文件不存在或路径组件不是目录)不报告错误,所以会有这样的情况,你会得到错误的数字,但不会收到一条错误消息告诉您原因。要解决此问题,您可以检查对目录的读取和搜索访问权限并相应地报告错误。

  7. [[ ... == pattern ]]操作者期望的文件名/通配符模式,而不是正则表达式。在 Bourne/POSIXsh语法中,您将case为此使用一个语句。通配符等效于^\.is .*,但在这里,您匹配完整路径,因此将是(作为正则表达式): /\.[^/]*$,除非您使用ksh扩展模式,否则您无法将其转换为通配符模式。

    相反,您可以使用:

    case ${fil##*/} in
      (.*) ...
    esac
    
    Run Code Online (Sandbox Code Playgroud)

    在 POSIXsh语法中。

因此,考虑到所有这些,您可以改为编写它(使用 POSIXsh语法):

hiddenDirs=0 hiddenNonDirs=0 dirs=0 nonDirs=0
isDir() {
  [ -d "$1" ] && [ ! -L "$1" ]
}
isHidden() {
  case ${1##*/} in
    (.*) return 0
  esac
  return 1
}
listAllFiles() {
  for f do
    ls -ld -- "$f" || continue
    if isDir "$f"; then
      if isHidden "$f"; then
        hiddenDirs=$(($hiddenDirs + 1))
      else
        dirs=$(($dirs + 1))
      fi
      if [ ! -r "$f" ]; then
        printf >&2 'Error: %s directory not readable\n' "$f"
        continue
      fi
      if [ ! -x "$f" ]; then
        printf >&2 'Error: %s directory not searchable\n' "$f"
        continue
      fi
      set -- "$f"/[*] "$f"/*
      if [ "$#" -eq 2 ] &&  [ "$1" != "$2" ]; then
        shift 2
      else
        shift
      fi
      n=$#
      set -- "$f"/.[*] "$f"/.* "$@"
      if [ "$(($# - $n))" -eq 2 ] &&  [ "$1" != "$2" ]; then
        shift 2
      else
        shift
        for f do
          case ${f##*/} in
            (.|..) ;;
            (*) set -- "$@" "$f"
          esac
          shift
        done
      fi
      listAllFiles "$@"
    else
      if isHidden "$f"; then
        hiddenNonDirs=$(($hiddenNonDirs + 1))
      else
        nonDirs=$(($nonDirs + 1))
      fi
    fi
  done
}
Run Code Online (Sandbox Code Playgroud)