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
Data de Publicação: 12 de março de 2020
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 e nas opções do comando composto case, por exemplo. Em todos esses casos, inclusive quando trabalhamos com nomes de arquivos, eles serão utilizados para realizar casamentos de padrões 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 regulares, 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 interessantes 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 especial $?
, 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:
*
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.
?(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