Percursos
/
Bash
Bash
/
Programa
/
A importância das aspas
A

A importância das aspas em Bash

{one: "1 exercício", many: "%{count} exercícios", other: "%{count} exercícios"}

Sobre A importância das aspas

Um conselho que os novos programadores de bash vão ouvir vezes sem conta:

Coloca as tuas variáveis entre aspas!

Porquê?

Quando expands uma variável (como $name), quase sempre queres usar o valor da variável como uma única string (ou número). Não queres que o valor seja, inesperadamente, tratado como duas ou mais strings separadas.

As aspas

Colocar entre aspas é o mecanismo principal para garantir que um trecho de texto é tratado como uma única palavra. Há dois tipos principais de aspas.

Aspas duplas

As aspas duplas permitem a expansão de parâmetros, a expansão aritmética e a substituição de comandos dentro delas.

echo "This is bash version $BASH_VERSION"

Podes incluir uma aspa dupla literal dentro de uma string delimitada por aspas duplas, precedendo-a com uma barra invertida.

echo "She said \"Let me teach you about bash.\""

Aspas simples

As aspas simples não permitem nenhuma expansão dentro delas.

echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION

Não é possível incluir uma aspa simples dentro de uma string delimitada por aspas simples.

A importância das aspas

O que é que está exatamente mal com echo $name?

Quando o shell analisa uma linha de código, realiza vários tipos de expansões, por ordem. Duas das últimas expansões são a divisão de palavras e a expansão de nomes de ficheiros.

Divisão de palavras

Todos os resultados de expansão de parâmetros, substituição de comandos ou expansão aritmética que não tenham ocorrido dentro de aspas duplas estão sujeitos à "divisão de palavras". O shell pega no resultado expandido e divide-o por qualquer sequência de carateres presente no valor de $IFS. Por omissão, são o espaço, a tabulação e a mudança de linha.

Podemos ver isto em ação com um ciclo do shell:

sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^

produz

1 The quick brown fox jumps.

Mas sem aspas:

i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^

produz

1 The
2 quick
3 brown
4 fox
5 jumps.

Vamos ver com um valor diferente de $IFS:

csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done

produz

first
second
third
Desativar a divisão de palavras

Uma técnica para desativar a divisão de palavras é definir $IFS como uma string vazia

sentence="The quick brown fox jumps."
IFS=    # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^

produz

1 The quick brown fox jumps.

No entanto, deixar as variáveis sem aspas não é recomendado, a menos que também desatives a expansão de nomes de ficheiros. Vamos ver como fazer isso mais abaixo.

Expansão de nomes de ficheiros

Os padrões "glob" servem para expressar um padrão conciso que corresponde a um conjunto de ficheiros. Por exemplo, podes fazer corresponder todos os ficheiros Markdown de um diretório com *.md. Os padrões glob existem desde a invenção do Unix.

Os carateres especiais do glob são:

  • * corresponde a zero ou mais carateres de qualquer tipo,

  • ? corresponde a exatamente um caráter de qualquer tipo,

  • [...] corresponde a exatamente um caráter do conjunto de carateres listados dentro dos parênteses retos.

    Advanced
    Há muitos detalhes sobre os conjuntos de carateres. Clica aqui para os ver.
    • [abc] corresponde a exatamente um caráter que seja um a, um b ou um c.
      • para incluir um parêntese reto de fecho literal na expressão, coloca-o como primeiro caráter a seguir ao parêntese de abertura (depois de um ^ opcional)
        • [][] é um conjunto de carateres que corresponde a um parêntese de fecho ou de abertura.
        • ou escapa-o com uma barra invertida: [[\]]
      • [x-y] corresponde a exatamente um caráter que esteja no intervalo de x a y
        • para incluir um hífen literal num conjunto de carateres, tem de ser o primeiro caráter a seguir ao parêntese de abertura (depois de um ^ opcional) ou o último antes do parêntese de fecho.
          • [-xy] ou [xy-] correspondem cada um a exatamente um caráter que seja um -, um x ou um y
          • ou escapa-o com uma barra invertida: [x\-y]
        • podem especificar-se vários intervalos, por exemplo, [0-9A-Fa-f] corresponde a um algarismo hexadecimal
      • [^abc] corresponde a exatamente um caráter que NÃO seja um a, um b ou um c,
      • [^x-y] corresponde a exatamente um caráter que NÃO esteja no intervalo de x a y
      • [[:character_class:]] corresponde a um caráter que pertence à "character_class" indicada
        • as classes de carateres disponíveis são:
          • lower -- letras minúsculas
          • upper -- letras maiúsculas
          • alpha -- letras
          • digit -- algarismos
          • alnum -- letras e algarismos
          • space -- espaços em branco
          • blank -- espaços em branco horizontais (espaço e tabulação)
          • xdigit -- algarismos hexadecimais
          • word -- carateres permitidos num identificador (letras, números, underscore)
          • punct -- pontuação
          • cntrl -- carateres de controlo
          • graph -- carateres "visíveis"
          • print -- carateres visíveis mais o espaço
        • podem combinar-se:
          • a classe [[:alpha:]] é composta por [[:lower:][:upper:]]
          • a classe [[:alnum:]] é composta por [[:alpha:][:digit:]]
          • a classe [[:word:]] é composta por [[:alnum:]_]
          • o underscore (octal 137) pertence tanto a [[:word:]] como a [[:punct:]]
          • a classe [[:graph:]] é composta por [[:alnum:][:punct:]]
          • a classe [[:print:]] é composta por [[:graph:] ] -- apenas o espaço (octal 040), não qualquer outro espaço em branco.
Iterar sobre uma lista de ficheiros

Podes iterar sobre uma lista de ficheiros com um ciclo for

for file in *.csv; do
    do_something_with "$file"
done

Repara que a variável "$file" está entre aspas. O padrão glob pode devolver ficheiros cujo nome contém espaços. Temos de colocar a variável entre aspas para que o comando do_something_with receba exatamente um argumento, o nome do ficheiro.

Os padrões glob não são expressões regulares

Os padrões glob não são expressões regulares.

  • corresponder a exatamente um caráter
    • regex .
    • glob ?
  • corresponder a zero ou mais carateres
    • regex .*
    • glob *
      • o padrão glob .* significa "corresponder a um nome de ficheiro em que o primeiro caráter é um ponto literal seguido de zero ou mais carateres de qualquer tipo".
  • corresponder a um ou mais carateres
    • regex .+
    • glob ?*
  • os conjuntos de carateres das regex e os dos padrões glob funcionam da mesma forma.
Advanced

O Bash disponibiliza padrões estendidos que se aproximam mais das expressões regulares.

Aspas e expansão de nomes de ficheiros

Há alturas em que queres suprimir a expansão de nomes de ficheiros. Lidar com dados introduzidos pelo utilizador é uma dessas alturas. Considera este excerto de código

read -p "Enter something: " user_input
echo $user_input

Se o utilizador introduzir um asterisco (*), o que é que vai ser apresentado? A lista de ficheiros do diretório atual. Provavelmente não é isso que queres fazer.

Outro exemplo é passar padrões como argumentos a programas que esperam padrões. O programa tr é frequentemente usado para manipular texto. Para converter o valor de uma variável em minúsculas, podes fazer isto

var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'

produz

my puppet

Um erro comum é esqueceres-te de colocar os argumentos do tr entre aspas: tr [:upper:] [:lower:]

Caution

Porque é que isso é um problema? Os padrões glob sem aspas são expandidos para a lista de ficheiros que correspondem. Suponhamos que a pessoa que usa o teu script tem ficheiros com os nomes p, u e w no diretório atual:

touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]

Isto dá origem a um erro!

O padrão [:upper:] sem aspas corresponde a qualquer um de :, u, p, e ou r. O padrão [:lower:] sem aspas corresponde a qualquer um de :, l, o, w, e ou r. O Bash faz corresponder os ficheiros p e u ao primeiro padrão, e o ficheiro w ao segundo, e o tr é invocado assim

echo "$var" | tr p u w

E esse não é o número certo de argumentos para o tr.

Desativar a expansão de nomes de ficheiros

Podes usar o comando set para desativar a expansão de nomes de ficheiros.

echo *      # prints a list of files
set -f      # disable filename expansion
echo *      # prints a literal asterisk
set +f      # enable filename expansion
Controlar a expansão de nomes de ficheiros

Há várias opções do comando interno shopt que controlam a forma como a expansão de nomes de ficheiros funciona. Algumas das mais interessantes são:

  • shopt -s nocaseglob -- faz correspondência sem distinguir maiúsculas de minúsculas
  • shopt -s extglob -- ativa os padrões estendidos
  • shopt -s nullglob -- se nenhum ficheiro corresponder, substitui o padrão por nada. O comportamento por omissão é deixar o padrão no lugar como uma string literal.
Editar via GitHub A ligação abre numa nova janela ou separador