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

13

Recentemente, tive um pequeno contratempo causado por um padrão de shell que se expandiu de maneira inesperada. Eu queria mudar o dono de um monte de arquivos de ponto no diretório /root , então eu fiz

chown -R root .*

Naturalmente, o .* foi expandido para .. , o que foi um pouco desastroso.

Eu sei que em bash esse comportamento pode ser alterado alterando algumas opções do shell, mas com as configurações padrão existe algum padrão que se expandiria para cada arquivo de ponto no diretório, mas não para . e .. ?

    
por Ernest A 02.01.2014 / 01:12

2 respostas

19

Bash, ksh e zsh têm melhores soluções, 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 sem ponto. (Observe que [^.] é suportado por alguns shells, mas não todos, a sintaxe portátil para o complemento de conjunto de caracteres em padrões curinga é [!.] .) Portanto, ele exclui . e .. , mas também arquivos que começam com dois pontos . O padrão ..?* manipula arquivos que começam com dois pontos e não são apenas .. .

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

Este é o conjunto de padrões clássicos para corresponder a todos os arquivos:

* .[!.]* ..?*

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

  • Use * .* para enumerar todas as entradas e excluir . e .. em um loop. Um ou ambos os padrões podem não corresponder a nada, portanto, o loop precisa verificar a existência de cada arquivo. Você tem a oportunidade de filtrar outros critérios; por exemplo, remova o teste -h se quiser 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. Note que os parâmetros posicionais são burlados (os shells POSIX não possuem matrizes); coloque isso em uma função separada se isso 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 dos padrões podem não corresponder a nada, portanto, precisamos verificar os arquivos existentes (incluindo links simbólicos pendentes).

    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 somente se ele corresponder a algo. Isso executa o comando apenas uma vez. Note que os parâmetros posicionais são burlados (as 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 o GNU ou o BSD, é fácil evitar a recursão com as opções -mindepth e -maxdepth . Com POSIX encontrar, é 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
    
por 02.01.2014 / 03:03
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 {} \;
    
por 02.01.2014 / 02:01