Padrão de nome de arquivo do shell que se expande para arquivos de ponto, mas não para `..`?

13

Recentemente, tive um pequeno acidente causado por um padrão de shell que se expandiu de maneira inesperada. Eu queria mudar o proprietário de vários arquivos de ponto no /rootdiretório, então fiz

chown -R root .*

Naturalmente, a .*expansão para a ..qual foi um desastre.

Eu sei que bashnesse comportamento pode ser alterado ajustando alguma opção de shell, mas com as configurações padrão existe algum padrão que se expandiria para todos os arquivos de ponto no diretório, mas não para .e ..?

Ernest A
fonte

Respostas:

20

Bash, ksh e zsh têm soluções melhores, mas nesta resposta eu assumo um shell POSIX.

O padrão .[!.]*corresponde a todos os arquivos que começam com um ponto seguido por um caractere que não é ponto. (Observe que [^.]é suportada por algumas shells, mas não por todas, a sintaxe portátil do complemento do conjunto de caracteres nos padrões curinga é [!.].) Portanto, ela exclui .e .., mas também arquivos que começam com dois pontos. O padrão ..?*lida com arquivos que começam com dois pontos e não são apenas ...

chown -R root .[!.]* ..?*

Este é o padrão clássico definido para corresponder a todos os arquivos:

* .[!.]* ..?*

Uma limitação dessa abordagem é que, se um dos padrões não corresponder a nada, ele será passado ao comando. Em um script, quando você deseja corresponder a todos os arquivos em um diretório, exceto , .e ..existem várias soluções, todas elas complicadas:

  • Use * .*para enumerar todas as entradas e excluir .e ..em um loop. Um ou ambos os padrões podem corresponder a nada; portanto, o loop precisa verificar a existência de cada arquivo. Você tem a oportunidade de filtrar por outros critérios; por exemplo, remova o -hteste se desejar pular links simbólicos pendentes.

    for x in * .*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
  • Uma variante mais complexa em que o comando é executado apenas uma vez. Observe que os parâmetros posicionais são derrotados (shells POSIX não possuem matrizes); coloque isso em uma função separada, se este for um problema.

    set --
    for x in * .[!.]* ..?*; do
      case $x in .|..) continue;; esac
      [ -e "$x" ] || [ -h "$x" ] || continue
      set -- "$@" "$x"
    done
    somecommand "$@"
  • Use o * .[!.]* ..?*tríptico. Novamente, um ou mais padrões podem não corresponder a nada; portanto, precisamos verificar os arquivos existentes (incluindo links simbólicos danificados).

    for x in * .[!.]* ..?*; do
      [ -e "$x" ] || [ -h "$x" ] || continue
      somecommand "$x"
    done
  • Use o * .[!.]* ..?*tryptich e execute o comando uma vez por padrão, mas apenas se corresponder a algo. Isso executa o comando apenas uma vez. Observe que os parâmetros posicionais são derrotados (shells POSIX não possuem matrizes); coloque isso em uma função separada, se isso for um problema.

    set -- *
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- .[!.]* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    set -- ..?* "$@"
    [ -e "$1" ] || [ -h "$1" ] || shift
    somecommand "$@"
  • Use find. Com a localização GNU ou BSD, é fácil evitar recursões com as opções -mindepthe -maxdepth. Com o POSIX find, é um pouco mais complicado, mas pode ser feito. Este formulário tem a vantagem de permitir facilmente executar o comando uma única vez, em vez de uma vez por arquivo (mas isso não é garantido: se o comando resultante for muito longo, o comando será executado em vários lotes).

    find . -name . -o -exec somecommand {} + -o -type d -prune
Gilles 'SO- parar de ser mau'
fonte
6

Isso funciona se todos os nomes de arquivos contiverem pelo menos três caracteres (incluindo o ponto):

chown -R root .??*

Para uma solução mais robusta, você pode usar find:

find . -maxdepth 1 -name '.*' -exec chown -R root {} \;

fonte