Como detectar se um arquivo está oculto

5

Eu escrevi este código como parte de algum curso, onde temos que criar um contador de arquivos recursivo sem usar os comandos find, du ou -R. Isso parece funcionar, mas sempre retorna o hiddenfilecounter como 0. Existe alguma razão específica para isso não estar funcionando.

#!/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
    
por Joshua Twaites 05.01.2014 / 19:13

3 respostas

7

Existem algumas coisas erradas aqui. Primeiro, você está executando /bin/sh quando realmente deseja executar /bin/bash .

Agora, a parte do seu script que verifica arquivos ocultos é

elif [[ -f $file &&  "$file" == ^. ]]; then

Se você executar seu script como foo.sh /home/foo , cada $file começará com /home/foo , de modo que nem os arquivos ocultos começarão com pontos. Além disso, você não pode usar ^ com == , ele corresponde ao início de qualquer maneira (veja aqui ).

De qualquer forma, uma implementação funcional do seu script seria

#!/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

No entanto, essa não é uma maneira muito boa de fazer isso, você não pode lidar com mais de um diretório como entrada, e, mais importante, verificar apenas se $bn é um diretório oculto, nunca $file , o que significa você nunca encontrará mais de um diretório oculto e estará tornando desnecessariamente complicado. Você poderia simplificar muito usando globstar . Eu escreveria assim:

#!/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
    
por 05.01.2014 / 19:51
5

Existem vários problemas com o seu código:

  1. Se você especificar /bin/sh como o she-bang, deverá escrever seu script na sintaxe sh (ou seja, a sintaxe POSIX sh ou a sintaxe Bourne shell se você planeja ser portável para sistemas muito antigos). Não há shopts em sh de sintaxe ( shopts é bash específico), nem [[ ... ]] (que é uma construção ksh também disponível em zsh e bash ), nem local , nem let .

    Basicamente, seu script só funcionaria com bash , portanto, você deveria usar um #! /bin/bash - she-bang (mas seu script não funcionaria em sistemas em que bash não estivesse instalado) ou convertê-lo para a sintaxe sh .

  2. Deixar uma variável sem aspas no contexto da lista, como nos argumentos para um comando (como basename $file ), é o operador split + glob. Você não quer fazer isso aqui.

  3. Ao passar dados arbitrários para um comando, você precisa usar -- para separar os argumentos das opções. Caso contrário, um nome de arquivo começando com - seria considerado como uma opção. Então: bn=$(basename -- "$file")

  4. Há uma confusão em relação a -d e -f comum com pessoas vindas do Microsoft Word. No Unix, tudo (a maioria das coisas) é um arquivo. Existem muitos tipos diferentes de arquivos: diretórios, links simbólicos, arquivos regulares, fifos, soquetes, dispositivos e muitos outros, dependendo da variante atual do Unix.

    [[ -d ... ]] retornará verdadeiro se o arquivo for do tipo diretório, [[ -f ... ]] se o arquivo for do tipo regular . E os outros tipos de arquivos? Também no caso de symlinks [[ -d ... ]] retornará true se o arquivo for do tipo symlinks, mas eventualmente for resolvido para um arquivo do tipo diretório . O que isso significa é que, por exemplo, se houver um link simbólico apontando para / , o script será executado indefinidamente.

    Seria como se estivesse usando find -L (anteriormente, find -follow ), exceto que find -L é capaz de detectar loops. Pelo mesmo motivo, você não pode usar bash do globstar (embora você possa usar zsh ou ksh93 equivalente que não tenha o mesmo problema) a menos que o bash seja versão 4.3 ou superior. / p>

    Então, você quer considerar arquivos de diretório versus não-diretório? Ou arquivos diretório e regulares apenas, ignorando todo o resto?

    No primeiro caso, para testar o diretório, é [ -d "$file" ] && [ ! -L "$file" ] e, para não-diretório: [ -L "$file" ] || [ ! -d "$file" ] .

    No segundo caso: [ -d "$file" ] && [ ! -L "$file" ] e [ -f "$file" ] && [ ! -L "$file" ] .

  5. Deve-se notar que você conta mais entradas de diretório do que arquivos. Por exemplo, se um arquivo aparecer como duas entradas de diretório diferentes (dois hardlinks), ele será contado duas vezes. Há também duas entradas de diretório que você não está contando: . e .. (embora você provavelmente não queira contá-las).

  6. O comando [ ou o [[ ... ]] construct e o shell globbing não relatam um erro quando não conseguem acessar um arquivo (por falta ou permissão ou porque o arquivo não existe ou componentes do caminho) não são diretórios), portanto, haverá casos em que você receberá o número errado, mas não receberá uma mensagem de erro informando o motivo. Para contornar isso, você pode verificar o acesso de leitura e pesquisa aos diretórios e relatar erros de acordo.

  7. O operador [[ ... == pattern ]] espera um padrão de nome de arquivo / curinga, não uma expressão regular. Na sintaxe Bourne / POSIX sh , você usaria uma instrução case para isso. O equivalente curinga de ^\. é .* , mas aqui você está combinando com o caminho completo, de modo que seria (como regex): /\.[^/]*$ , que você não pode converter em padrão curinga a menos que use ksh padrão estendido.

    Em vez disso, você pode usar:

    case ${fil##*/} in
      (.*) ...
    esac
    

    na sintaxe% POSI sh .

Então, com tudo isso em mente, você poderia escrevê-lo (em POSIX sh syntax):

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
}
    
por 06.01.2014 / 23:04
0

Para localizar os arquivos de ponto basename deve ser aplicado à variável $ file. por exemplo. Um caminho /tmp/.dotone , por exemplo, não possui um primeiro caractere de '.'

O extrato de código usa basename para atribuir a variável f com o nome do arquivo e, em seguida, usa ${f:0:1} para localizar o primeiro caractere e comparar com '.'.

for file in "$dir"/*; do
    f=$(basename $file)
     if [[ $bn == .* ]]; then
     let hiddenDirectoryCounter+=1
     listAllFiles "$file"
   elif [[ -f $file &&  "${f:0:1}" = '.' ]]; then
     ls -l $file
     let hiddenFileCounter+=1
   elif [[ -f $file ]];then
       ...
       ...
    
por 05.01.2014 / 19:45

Tags