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.
Colocar entre aspas é o mecanismo principal para garantir que um trecho de texto é tratado como uma única palavra. Há dois tipos principais de aspas.
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.\""
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.
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.
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
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.
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.
[abc] corresponde a exatamente um caráter que seja um a, um b ou um c.
^ opcional)
[][] é um conjunto de carateres que corresponde a um parêntese de fecho ou de abertura.[[\]]
[x-y] corresponde a exatamente um caráter que esteja no intervalo de x a y
^ 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
[x\-y]
[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
lower -- letras minúsculasupper -- letras maiúsculasalpha -- letrasdigit -- algarismosalnum -- letras e algarismosspace -- espaços em brancoblank -- espaços em branco horizontais (espaço e tabulação)xdigit -- algarismos hexadecimaisword -- carateres permitidos num identificador (letras, números, underscore)punct -- pontuaçãocntrl -- carateres de controlograph -- carateres "visíveis"print -- carateres visíveis mais o espaço[[:alpha:]] é composta por [[:lower:][:upper:]]
[[:alnum:]] é composta por [[:alpha:][:digit:]]
[[:word:]] é composta por [[:alnum:]_]
[[:word:]] como a [[:punct:]]
[[:graph:]] é composta por [[:alnum:][:punct:]]
[[:print:]] é composta por [[:graph:] ] -- apenas o espaço (octal 040), não qualquer outro espaço em branco.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.
.
?
.*
*
.* 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"..+
?*
O Bash disponibiliza padrões estendidos que se aproximam mais das expressões regulares.
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:]
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.
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
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úsculasshopt -s extglob -- ativa os padrões estendidosshopt -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.