De acordo com as Leis 12.965/2014 e 13.709/2018, que regulam o uso da Internet e o tratamento de dados pessoais no Brasil, ao me inscrever na newsletter do portal DICAS-L, autorizo o envio de notificações por e-mail ou outros meios e declaro estar ciente e concordar com seus Termos de Uso e Política de Privacidade.
Colaboração: Blau Araújo
No Bash, quando usamos os caracteres coringa (*wildcards*) *
, ?
e [ ]
para casar padrões de nomes de arquivos, o que acontece é uma das várias expansões do shell, a expansão de nomes de arquivos.
Isso quer dizer que o Bash irá expandir esses símbolos para nomes de arquivos que correspondam ao padrão definido e, apenas depois, o comando que utilizará esses nomes de arquivos como argumentos será executado. Por exemplo...
:~$ ls arquivo1.doc arquivo2.doc teste1.txt teste2.txt :~$ ls *.txt teste1.txt teste2.txt
Aqui, o Bash expandiu *.txt
para teste1.txt
e teste2.txt
, e passou para o comando ls
esses dois nomes como argumentos. Do "ponto de vista" do ls
, ele nunca chegou a ver *.txt
, mas apenas o resultado da expansão, como na linha abaixo:
:~$ ls teste1.txt teste2.txt
E a prova disso é a saída do comando:
:~$ echo *.txt
teste1.txt teste2.txt
Como echo
não tem nada a ver com listagem de arquivos, podemos entender facilmente que ele só está exibindo uma expansão do shell, a expansão de nomes de arquivos.
A rigor, no caso específico do uso dos coringas para casar com nomes de arquivos, nós podemos dizer que estamos fazendo um globbing, que é como chamamos qualquer casamento de padrões com nomes de arquivos, mas este não é o único uso dos coringas no shell.
Os mesmos símbolos e sintaxes também podem ser usados em outros comandos e expansões do shell, como nas [expansões de parâmetros](https://debxp.org/cbpb/aula10) e nas opções do comando composto [case](https://debxp.org/cbpb/aula14#a_estrutura_case), por exemplo. Em todos esses casos, inclusive quando trabalhamos com nomes de arquivos, eles serão utilizados para realizar [casamentos de padrões](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html#Pattern-Matching) de texto (caracteres, strings, etc).
Por padrão, o Bash vem configurado para permitir o uso dos coringas de que falamos acima (*
, ?
e [ ]
), mas estes não são os únicos recursos que ele oferece para o casamento de padrões. Através do comando bultin shopt
, nós podemos listar e alterar o estado das diversas outras opções relativas ao *globbing*. Esta é a sintaxe do comando shopt
:
shopt [-pqsu] [-o] [NOME-OPÇÃO ...] Opções: -o restringe NOME-OPÇÃO àqueles definidos para usar com `set -o' -p imprime cada opção shell com uma indicação de seu status -q suprime a saída -s habilita (set) com NOME-OPÇÃO -u desabilita (unset) com NOME-OPÇÃO
Vamos entender melhor essas opções do comando shopt
:
Opção | Descrição |
---|---|
-o |
Algumas opções do shell precisam ser gerenciadas com o comando set -o , mas nós podemos listá-las com o shopt utilizando a opção -o . |
-p |
As opções do shell serão exibidas no formato shopt -s\|-u NOME-OPÇÃO , onde -s indica que ela está habilitada e -u indica que está desabilitada. |
-q |
Não exibe uma saída, mas retorna status 0 (sucesso) quando a opção está habilitada ou 1 (erro), quando está desabilitada. |
-s |
Habilita (*set*) a opção do shell. |
-u |
Desabilita (*unset*) a opção do shell. |
Além dessas opções, executando o comando shopt
(ou shopt -o
) sem argumentos, nós temos a lista de todas as opções do shell seguidas de *on* ou *off*, indicando se estão ligadas ou desligadas.
Por padrão, como já vimos, nós podemos contar com os seguintes símbolos para casar padrões no Bash:
Símbolo | Padrão |
---|---|
* |
Casa com zero ou mais caracteres quaisquer |
? |
Casa com apenas um caractere existente |
[ ] |
Lista de caracteres permitidos na posição |
NOTA: Repare que eles não possuem o mesmo significado de seus semelhantes nas expressões(https://aurelio.net/regex/guia/), onde *
e ?
seriam quantificadores e a lista [ ]
seria um representante.
Além desses, o Bash também oferece algumas sintaxes opcionais muito interessantes e úteis que podem ampliar em muito a nossa capacidade de casar padrões. São os chamados globs estendidos ou extglobs.
Então, vamos listar as opções do shell relacionadas ao globbing. Esta seria uma das formas:
:~$ shopt | grep glob
dotglob off
extglob on
failglob off
globasciiranges on
globstar off
nocaseglob off
nullglob off
Para ver as opções de globbing que devem ser gerenciadas com o comando set -o
...
:~$ shopt -o | grep glob
noglob off
Vejamos o que cada uma delas faz e qual a configuração geralmente encontrada em sistemas GNU/Linux.
Opção | Descrição | Shell interativo | Scripts |
---|---|---|---|
dotglob |
Permite o casamento de padrões com nomes de arquivos iniciados com ponto (arquivos ocultos) sejam expandidos. | Desligada | Desligada |
extglob |
Permite o uso de sintaxes adicionais para casamento de padrões. | Ligada | Desligada |
failglob |
Faz com que padrões sem correspondência gerem uma mensagem de *erro de expansão*. | Desligada | Desligada |
globasciiranges |
Define que o conjunto de caracteres que irão corresponder às faixas definidas dentro dos colchetes ([ ] ) não seguirão as definições de sequências caracteres do idioma (*locale*), e sim as sequências de caracteres ASCII. |
Ligada | Ligada |
globstar |
No contexto do casamento de nomes de arquivos, permite o uso de dois asteriscos (** ) para incluir casamentos com nomes de diretórios e subdiretórios, ou apenas de diretórios e subdiretórios se vier seguido da barra (/ ). |
Desligada | Desligada |
nocaseglob |
Permite casar padrões independente dos caracteres estarem em caixa alta ou baixa. | Desligada | Desligada |
nullglob |
Quando não é encontrado um nome de arquivo que corresponda ao padrão definido, o shell retornará o próprio padrão na forma de uma string. Com esta opção habilitada, ele retornará uma string nula. | Desligada | Desligada |
noglob |
(opção gerenciada com set -o ) Se habilitada, não permite expansões de nomes de arquivos. |
Desligado | Desligado |
No caso específico do casamento de padrões de texto, como nas opções de uma declaração case
, por exemplo, ainda é possível ignorar diferenças de caixa alta ou baixa atrvés da opção do shell nocasematch
, que vem desabilitada tanto para o shell interativo (terminal) quanto para o não-interativo (scripts).
Exemplo:
#!/usr/bin/env bash # Habilita a opção 'nocasematch'... shopt -s nocasematch case $1 in banana) echo "Você digitou $1";; laranja) echo "Você digitou $1";; *) echo "Digite 'laranja' ou 'banana'...";; esac # Desabilita a opção 'nocasematch'... shopt -u nocasematch exit
Sem habilitar a opção nocasematch
, "BANANA" seria diferente de "banana", e o case
não reconheceria essa alternativa e cairia na opção *
. Mas, com ela habilitada, nós podemos digitar, por exemplo...
:~$ ./testeglob.sh laranJA Você digitou laranJA :~$ ./testeglob.sh BANANA Você digitou BANANA
IMPORTANTE! Existem formas mais(https://debxp.org/cbpb/aula10#maiusculas_e_minusculas) e eficazes de tratar caixas altas e baixas do que alterando a configuração do Bash, mas é válido você estar ciente da existência deste recurso.
Além das opções do shell, nós ainda podemos trabalhar com a variável GLOBIGNORE
para alterar a expansão de nomes de arquivos. Com ela, você pode listar os padrões, separados por dois pontos (:
), que representarão o conjunto de nomes de arquivos que devem ser removidos do resultado da expansão.
Por exemplo, supondo um diretório com os arquivos exemplo1.txt
, exemplo2.txt
, exemplo3.png
...
:~$ ls e*
exemplo1.txt exemplo2.txt exemplo3.png
Definindo GLOBIGNORE
para ignorar *.png
...
:~$ GLOBIGNORE=*.png
:~$ ls e*
exemplo1.txt exemplo2.txt
Ou ainda...
:~$ GLOBIGNORE=*.png:*2*
:~$ ls e*
exemplo1.txt
NOTA: Apesar de bastante útil, também é possível ignorar padrões de nomes de arquivos com a opção do shell extglob
, como veremos a seguir, mas a variável especial GLOBIGNORE
é interessante pela possibilidade que oferece de listarmos vários padrões que queremos ignorar sem alterarmos as configurações do shell.
Como vimos, geralmente os sistemas vêm com o extglob
habilitado para o shell interativo mas desabilitado para o shell não-interativo. Por isso, caso você queira utilizar as sintaxes estendidas para casamento de padrões, é muito provável que seja necessário habilitar a opção extglob
no seu script, o que pode ser feito com o comando:
shopt -s extglob
Normalmente, não seria necessário restaurar o estado original do extglob
, já que o fim do script encerrará a sessão do shell em que estiver rodando e, com isso, o estado original seria automaticamente restaurado. Porém, é considerada uma boa prática garantir que o estado original de todas as configurações do shell seja restaurado assim que elas não forem mais necessárias, especialmente no caso do seu script ser chamado pelo comando source
. Então, para desabilitar o extglob
, nós podemos usar o comando:
shopt -u extglob
O código abaixo mostra uma forma bastante interessante de alternar o estado da opção extglob
:
shopt -q extglob; extglob_status=$? (($extglob_status)) && shopt -s extglob # ... seu código ... (($extglob_status)) && shopt -u extglob
Na primeira linha, o comando shopt -q extglob
retornará status 0
se extglob
estiver habilitado ou 1
, caso esteja desabilitado. Através da variável(https://debxp.org/cbpb/aula05#obtendo_o_status_de_saida_do_ultimo_comando) $?
, nós armazenamos o status retornado na variável extglob_status
.
Em seguida, nós utilizamos o comando composto (( ... ))
para avaliar o valor em extglob_status
. Se ele for 1
(extglob
desabilitado), a expressão retornará 0
e fará o comando shopt -s extglob
ser executado. Caso seja 0
(extglob
habilitado), a expressão retornará 1
e nada será feito.
IMPORTANTE! O comando composto (( ... ))
é utilizado para avaliar expressões aritméticas. Quando o valor da expressão avaliada for diferente de zero, ela retornará um status de saída 0
(sucesso). Mas, se o valor da expressão for igual a zero, ela retornará um status de saída 1
(erro). O nosso código está se aproveitando justamente dessa característica para simular um tipo de *flag*, indicando se extglob
está ligado (saída 1
) ou desligado (saída 0
).
Minha gratidão ao professor Julio Neves (o *Papai do Shell*) por me alertar da necessidade de explicar melhor este ponto.
Por último, após a execução do seu código, nós avaliamos novamente o valor em extglob_status
que, obviamente, armazena o estado original da opção extglob
. Se esse valor for 0
, significa que nada foi alterado na avaliação anterior. Mas, se for 1
significa que o estado de extglob
foi alterado, como vimos, e precisa ser restaurado, o que é feito com o comando shopt -u extglob
.
Os globs estendidos ampliam muito a nossa capacidade de representar padrões mais complexos, especialmente em situações onde não podemos utilizar expressões regulares. Abaixo, podemos ver as novas sintaxes que são adicionadas ao nosso repertório quando a opção extglob
está habilitada:
Sintaxe | Descrição |
---|---|
?(lista-de-padrões) |
Casa com zero ou uma ocorrência dos padrões na lista. |
*(lista-de-padrões) |
Casa com zero ou mais ocorrências dos padrões na lista. |
+(lista-de-padrões) |
Casa com uma ou mais ocorrências dos padrões na lista. |
@(lista-de-padrões) |
Casa com um dos padrões na lista. |
!(lista-de-padrões) |
Casa com tudo menos os padrões na lista. |
IMPORTANTE: Nas descrições acima, lista-de-padrões
é uma lista de representações de padrões separados por uma barra vertical (|
), o que pode ser lido como a palavra "ou": este padrão, ou este padrão, etc...
Vamos considerar uma pasta contendo os seguintes arquivos...
aa.txt a.txt .txt
Utilizando o padrão *.txt
, nós teríamos...
ls *.txt
aa.txt a.txt
Repare que aqui, como dissemos no começo, o comando ls
verá a expansão de *.txt
, que será aa.txt
e a.txt
, já que o shell, por padrão, não irá expandir nomes de arquivos iniciados com ponto (.
), a menos que ele venha explicitado antes dos caracteres coringa. Isso, por exemplo, expandiria o arquivo .txt
:
:~$ ls .*txt
.txt
Este comportamento é definido pela opção do shell dotglob
que, como vimos, vem desabilitada na maioria dos sistemas, mas pode ser habilitada através do comando:
shopt -s dotglob
Portanto, se habilitarmos o dotglob
...
:~$ shopt -s dotglob
:~$ ls *.txt
aa.txt a.txt .txt
:~$ shopt -u dotglob
Neste tópico, porém, nós trabalharemos com o comportamento padrão do Bash, já levando em conta que, ao menos para uso no terminal, é bem provável que a opção extglob
já esteja habilitada.
Voltando ao nosso exemplo, o padrão *.txt
seria expandido da seguinte forma:
ls *.txt
aa.txt a.txt
Utilizando os globs estendidos, nós poderíamos fazer coisas desse tipo:
# Casa apenas com zero ou um caractere 'a'... :~$ ls ?(a).txt a.txt .txt # Casa com zero ou mais caracteres 'a'... :~$ ls *(a).txt aa.txt a.txt .txt # Casa com um ou mais caracteres 'a'... :~$ ls +(a).txt aa.txt a.txt # Casa apenas com um único caractere 'a'... :~$ ls @(a).txt a.txt # Casa com tudo que não for apenas um 'a'... :~$ ls !(a).txt aa.txt
Repare que nos dois primeiros exemplos, ?(a).txt
e *(a).txt
, mesmo com dotglob
desabilitado, o Bash também expandiu o arquivo .txt
. Porém, no último exemplo, onde pedimos tudo que não casasse com o nome de arquivo a.txt
, o resultado foi apenas aa.txt
.
Esse tipo de inconsistência de comportamento é algo a que sempre devemos estar atentos. Quer dizer, nós temos duas regras para expansões que fazem basicamente a mesma coisa:
* Com *
e !(lista-de-padrões)
, o Bash não expande nomes de arquivos iniciados com ponto, a menos que a opção dotglob
esteja habilitada.
* Com ?(lista-de-padrões)
e *(lista-de-padrões)
, ele já expande nomes de arquivos iniciados com ponto (.
), mesmo com dotglob
desabilitado.
> NOTA: Uma possível explicação é que, no caso do casamento dos padrões negados, é possível que o Bash primeiro expanda os nomes de arquivos como faria com o asterisco, caso em que arquivos com nomes iniciados com ponto não são expandidos, para só depois excluir os nomes que casem com o padrão e, a essa altura os arquivos iniciados com ponto já não estariam mais lá.
Apesar disso, aqui está um exemplo de globbing negado que, além de muito útil, é uma dúvida bastante comum: como listar todos os arquivos ocultos menos .
e ..
? Experimente isso:
ls -a .!(|.|..)
Sem o globbing, o comando ls -a
mostraria os arquivos ocultos (iniciados com ponto) nos resultados, inclusive .
e ..
. Para resolver isso, nós os incluímos na lista de padrões a serem ignorados. Contudo, sem o padrão vazio que pusemos no início da lista, o arquivo .
continuaria sendo exibido.
Assim como os globs normais (*
, ?
e [ ]
), os globs estendidos também podem ser utilizados em outras situações além do casamento de nomes de arquivos. Vejamos um exemplo:
:~$ fruta=abacate :~$ [[ $fruta == abaca+(te|xi) ]] && echo "casou" || echo "não casou" casou :~$ fruta=abacaxi :~$ [[ $fruta == abaca+(te|xi) ]] && echo "casou" || echo "não casou" casou :~$ fruta=abacaba :~$ [[ $fruta == abaca+(te|xi) ]] && echo "casou" || echo "não casou" não casou
Aqui, nós utilizamos o padrão abaca+(te|xi)
para testar (comando [[ ... ]]
) se a variável fruta
continha as palavras abacate
ou abacaxi
pela definição do quais seriam os dois caracteres válidos após abaca
: te
ou xi
.
Sem extglob
habilitado, a única forma de fazer esse mesmo tipo de teste seria com o operador =~
, que avalia uma expressão regular, por exemplo:
:~$ fruta=abacate :~$ [[ $fruta =~ abaca(te|xi) ]] && echo "casou" || echo "não casou" casou :~$ fruta=abacaba :~$ [[ $fruta =~ abaca(te|xi) ]] && echo "casou" || echo "não casou" não casou
Também podemos utilizar globs estendidos nas opções de uma declaração case
, como no script exemplo.sh
abaixo:
#!/usr/bin/env bash # Habilitando o 'extglob' se for preciso... shopt -q extglob; extglob_status=$? (($extglob_status)) && shopt -s extglob case $1 in !(*.gif|*.jpg|*.png)) echo "$1 não é imagem!";; *) echo "$1 é uma imagem";; esac # Restaurando o estado original de 'extglob'... (($extglob_status)) && shopt -s extglob exit
Com esse script, nós estamos verificando se o usuário digitou ou não alguma das três extensões válidas como imagens: .gif
, .jpg
e .png
. Caso o parâmetro passado para o script não corresponda a um nome de arquivo com uma dessas três extensões, a saída será a mensagem: ...não é uma imagem
.
Isso é feito com o padrão estendido !(*.gif|*.jpg|*.png)
, onde estão listados os três padrões que serão negados (lembre-se do tópico anterior sobre a variável GLOBIGNORE
). Qualquer coisa que não corresponda a um dos três padrões fará com que o case
execute os comandos desta opção. Mas, se o usuário digitar qualquer coisa que termine com um desses padrões, a opção executada será a do *
, que aceita nada ou qualquer coisa.
Executando...
:~$ ./exemplo.sh teste.txt teste.txt não é imagem! :~$ ./exemplo.sh teste.png teste.png é uma imagem :~$ ./exemplo.sh teste.jpg teste.jpgé uma imagem :~$ ./exemplo.sh teste.gif teste.gif é uma imagem
Como vimos, as sintaxes estendidas para casamento de textos e nomes de arquivos podem ser muito úteis em algumas situações. Obviamente, se compatibilidade e portabilidade estiverem entre os critérios do seu projeto, pode ser mais conveniente pensar em outras alternativas, como o uso de ferramentas como grep
, sed
e awk
, por exemplo. Porém, tendo a certeza da compatibilidade, não há dúvida de que esse recurso pode nos ajudar a construir scripts mais simples e com alto desempenho.
Este artigo foi publicado originalmente no portal Gitlab.com
This policy contains information about your privacy. By posting, you are declaring that you understand this policy:
This policy is subject to change at any time and without notice.
These terms and conditions contain rules about posting comments. By submitting a comment, you are declaring that you agree with these rules:
Failure to comply with these rules may result in being banned from submitting further comments.
These terms and conditions are subject to change at any time and without notice.
Comentários