Os programadores usam regularmente o bash para resolver muitas tarefas relacionadas ao desenvolvimento de software. Ao mesmo tempo, matrizes bash são frequentemente consideradas um dos recursos mais incompreensíveis desse shell (provavelmente, matrizes perdem apenas para expressões regulares a esse respeito). O autor do material, cuja tradução publicamos hoje, convida todos para o maravilhoso mundo das matrizes bash, que, se você se acostumar com a sintaxe incomum, podem trazer muitos benefícios.
O verdadeiro desafio que os arrays do bash são úteis
Escrever sobre o bash é controverso. O fato é que os artigos sobre o bash geralmente se transformam em guias do usuário dedicados a histórias sobre os recursos sintáticos dos comandos em questão. Este artigo foi escrito de forma diferente. Esperamos que você não o encontre no próximo "manual do usuário".
Dado o exposto, imagine um cenário real para o uso de matrizes no bash. Suponha que você tenha a tarefa de avaliar e otimizar um utilitário a partir de um novo conjunto interno de ferramentas usadas em sua empresa. Na primeira etapa deste estudo, você precisa testá-lo com diferentes conjuntos de parâmetros. O teste tem como objetivo estudar como um novo conjunto de ferramentas se comporta quando eles usam um número diferente de threads. Para simplificar a apresentação, assumimos que a “caixa de ferramentas” é uma “caixa preta” compilada a partir do código C ++. Ao usá-lo, o único parâmetro que podemos influenciar é o número de threads reservados para o processamento de dados. Chamar o sistema sob investigação na linha de comando é assim:
./pipeline
O básico
Antes de tudo, declaramos uma matriz que contém os valores do parâmetro
--threads
com o qual queremos testar o sistema. Essa matriz é assim:
allThreads=(1 2 4 8 16 32 64 128)
Neste exemplo, todos os elementos são números, mas, de fato, em matrizes bash, você pode armazenar números e seqüências de caracteres ao mesmo tempo. Por exemplo, a declaração de uma matriz desse tipo é bastante aceitável:
myArray=(1 2 "three" 4 "five")
Como com outras variáveis do bash, verifique se não há espaços ao redor do sinal
=
. Caso contrário, o bash considerará o nome da variável o nome do programa que ele precisa executar e
=
o primeiro argumento!
Agora que inicializamos a matriz, vamos extrair alguns elementos dela. Aqui você pode observar, por exemplo, que o
echo $allThreads
produzirá apenas o primeiro elemento da matriz.
Para entender os motivos desse comportamento, vamos desviar um pouco das matrizes e relembrar como trabalhar com variáveis no bash. Considere o seguinte exemplo:
type="article" echo "Found 42 $type"
Suponha que você tenha uma variável do
$type
que contenha uma sequência que represente um substantivo. Após esta palavra, adicione a letra
s
. No entanto, você não pode simplesmente adicionar esta letra ao final do nome da variável, pois isso transformará o comando para acessar a variável em
$types
, ou seja, trabalharemos com uma variável completamente diferente. Nessa situação, você pode usar uma construção como o
echo "Found 42 "$type"s"
. Mas é melhor resolver esse problema usando colchetes:
echo "Found 42 ${type}s"
, que nos permite dizer ao bash onde o nome da variável começa e termina (curiosamente, a mesma sintaxe é usada no JavaScript ES6 para incorporar variáveis em expressões em
seqüências de caracteres padrão ).
Agora de volta às matrizes. Acontece que, embora chaves geralmente não sejam necessárias ao trabalhar com variáveis, elas são necessárias para trabalhar com matrizes. Eles permitem que você defina índices para acessar os elementos da matriz. Por exemplo, um comando do formulário
echo ${allThreads[1]}
produzirá o segundo elemento da matriz. Se você esquecer as chaves na construção acima, o bash perceberá
[1]
como uma string e processará o que acontece de acordo.
Como você pode ver, as matrizes no bash têm uma sintaxe estranha, mas nelas, pelo menos, a numeração dos elementos começa do zero. Isso os torna semelhantes aos arrays de muitas outras linguagens de programação.
Maneiras de acessar elementos da matriz
No exemplo acima, usamos índices inteiros em matrizes especificadas explicitamente. Agora considere mais duas maneiras de trabalhar com matrizes.
O primeiro método é aplicável se precisarmos do
$i
ésimo elemento da matriz, em que
$i
é uma variável que contém o índice do elemento de matriz desejado. Você pode extrair esse elemento da matriz usando uma construção do formulário
echo ${allThreads[$i]}
.
O segundo método permite exibir todos os elementos da matriz. Consiste em substituir o índice numérico pelo símbolo
@
(pode ser interpretado como um comando apontando para todos os elementos da matriz). É assim:
echo ${allThreads[@]}
.
Iterando Elementos da Matriz em Loops
Os princípios acima, de trabalhar com elementos de matriz, serão úteis para resolver o problema de enumerar elementos de matriz. No nosso caso, isso significa iniciar o comando
pipeline
em estudo com cada um dos valores, que simboliza o número de threads e é armazenado em uma matriz. É assim:
for t in ${allThreads[@]}; do ./pipeline --threads $t done
Enumerando índices de matriz em loops
Agora considere uma abordagem ligeiramente diferente para classificar matrizes. Em vez de iterar sobre os elementos, podemos iterar sobre os índices da matriz:
for i in ${!allThreads[@]}; do ./pipeline --threads ${allThreads[$i]} done
Vamos analisar o que está acontecendo aqui. Como já vimos, uma construção no formato
${allThreads[@]}
representa todos os elementos da matriz. Quando adicionamos um ponto de exclamação aqui, transformamos essa construção em
${!allThreads[@]}
, o que leva ao fato de que ele retorna os índices da matriz (de 0 a 7 no nosso caso).
Em outras palavras, o loop
for
sobre todos os índices da matriz representada como a variável
$i
, e no corpo da loop, os elementos da matriz que servem como valores do parâmetro
${allThreads[$i]}
são
--thread
usando a construção
${allThreads[$i]}
.
A leitura deste código é mais difícil que a do exemplo anterior. Portanto, surge a questão de para que servem todas essas dificuldades. E precisamos disso porque, em algumas situações, ao processar matrizes em loops, você precisa conhecer os índices e os valores dos elementos. Por exemplo, se você precisar pular o primeiro elemento de uma matriz, a iteração sobre os índices nos salvará, por exemplo, da necessidade de criar uma variável adicional e de incrementá-la em um loop para trabalhar com os elementos da matriz.
Matrizes de preenchimento
Até agora, exploramos o sistema invocando o comando
pipeline
e passando cada um dos valores do parâmetro
--threads
nos interessa. Agora, suponha que este comando dê a duração de um determinado processo em segundos. Gostaríamos de interceptar os dados retornados a cada iteração e salvá-los em outra matriz. Isso nos dará a oportunidade de trabalhar com os dados armazenados após o término de todos os testes.
Construções úteis de sintaxe
Antes de falarmos sobre como adicionar dados às matrizes, vejamos algumas construções de sintaxe úteis. Para começar, precisamos de um mecanismo para obter a saída de dados por comandos bash. Para capturar a saída de um comando, você precisa usar a seguinte construção:
output=$( ./my_script.sh )
Após executar este comando, o que o
myscript.sh
$output
será armazenado na variável
$output
.
A segunda construção, que será útil muito em breve, permite anexar novos dados às matrizes. É assim:
myArray+=( "newElement1" "newElement2" )
Resolução de problemas
Agora, se você reunir tudo o que acabamos de aprender, poderá criar um script para testar o sistema, que executa um comando com cada um dos valores de parâmetro da matriz e armazena na outra matriz o que esse comando exibe.
allThreads=(1 2 4 8 16 32 64 128) allRuntimes=() for t in ${allThreads[@]}; do runtime=$(./pipeline --threads $t) allRuntimes+=( $runtime ) done
O que vem a seguir?
Acabamos de examinar como usar matrizes bash para iterar sobre os parâmetros usados ao iniciar um programa e salvar os dados que esse programa retorna. No entanto, as opções para usar matrizes não se limitam a esse cenário. Aqui estão mais alguns exemplos.
Alertas de Problemas
Nesse cenário, examinaremos um aplicativo dividido em módulos. Cada um desses módulos possui seu próprio arquivo de log. Podemos escrever um script de tarefa
cron
que, se forem encontrados problemas no arquivo de log correspondente, notificará por e-mail a pessoa responsável por cada um dos módulos:
# - logPaths=("api.log" "auth.log" "jenkins.log" "data.log") logEmails=("jay@email" "emma@email" "jon@email" "sophia@email") # for i in ${!logPaths[@]}; do log=${logPaths[$i]} stakeholder=${logEmails[$i]} numErrors=$( tail -n 100 "$log" | grep "ERROR" | wc -l ) # 5 if [[ "$numErrors" -gt 5 ]]; then emailRecipient="$stakeholder" emailSubject="WARNING: ${log} showing unusual levels of errors" emailBody="${numErrors} errors found in log ${log}" echo "$emailBody" | mailx -s "$emailSubject" "$emailRecipient" fi done
Solicitações de API
Suponha que você queira coletar informações sobre quais usuários comentam suas postagens no Medium. Como não temos acesso direto ao banco de dados deste site, não discutiremos consultas SQL. No entanto, você pode usar várias APIs para acessar esse tipo de dados.
Para evitar longas conversas sobre autenticação e tokens, usaremos como ponto de extremidade o serviço de teste de API público
JSONPlaceholder . Após receber uma publicação do serviço e extrair dados de seu código nos endereços de email dos comentaristas, podemos colocar esses dados em uma matriz:
endpoint="https://jsonplaceholder.typicode.com/comments" allEmails=() # 10 for postId in {1..10}; do # API response=$(curl "${endpoint}?postId=${postId}") # jq JSON allEmails+=( $( jq '.[].email' <<< "$response" ) ) done
Observe que a ferramenta
jq é usada aqui, o que permite analisar o JSON na linha de comandos. Não entraremos em detalhes sobre o trabalho com o jq, se você estiver interessado nesta ferramenta - consulte a documentação.
Bash ou Python?
Matrizes - um recurso útil e está disponível não apenas no bash. Quem escreve scripts para a linha de comando pode ter uma pergunta lógica sobre em quais situações vale a pena usar o bash e em que, por exemplo, Python.
Na minha opinião, a resposta a esta pergunta está em quanto o programador depende de uma tecnologia específica. Digamos, se o problema puder ser resolvido diretamente na linha de comando, nada impede o uso do bash. No entanto, no caso de, por exemplo, o script no qual você se interessar fazer parte de um projeto escrito em Python, você poderá usar o Python.
Por exemplo, para resolver o problema considerado aqui, você pode usar um script escrito em Python, no entanto, isso se resumirá em escrever wrappers para Python para bash:
import subprocess all_threads = [1, 2, 4, 8, 16, 32, 64, 128] all_runtimes = [] # for t in all_threads: cmd = './pipeline --threads {}'.format(t) # subprocess , p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) output = p.communicate()[0] all_runtimes.append(output)
Talvez a solução para esse problema com o bash, sem envolver outras tecnologias, seja mais curta e compreensível, e aqui você pode fazer completamente sem o Python.
Sumário
Neste material, analisamos muitos projetos usados para trabalhar com matrizes. Aqui está uma tabela onde você encontrará o que analisamos e algo novo.
Construção de sintaxe | Descrição do produto |
arr=() | Crie uma matriz vazia |
arr=(1 2 3) | Inicialização de matriz |
${arr[2]} | Obtendo o terceiro elemento de uma matriz |
${arr[@]} | Obtendo todos os elementos da matriz |
${!arr[@]} | Obtendo índices de matriz |
${#arr[@]} | Cálculo do tamanho da matriz |
arr[0]=3 | Substituindo o primeiro elemento de uma matriz |
arr+=(4) | Juntando uma matriz de valores |
str=$(ls) | Salvando a Saída do ls como uma Cadeia de Caracteres |
arr=( $(ls) ) | Salvando a saída do ls como uma matriz de nomes de arquivos |
${arr[@]:s:n} | Obtendo elementos da matriz de elemento com índice s para elemento com índice s+(n-1)
|
À primeira vista, os arrays do bash podem parecer bastante estranhos, mas as possibilidades que eles oferecem valem a pena lidar com essas esquisitices. Acreditamos que, ao dominar as matrizes do bash, você as usará com bastante frequência. É fácil imaginar inúmeros cenários nos quais essas matrizes podem ser úteis.
Caros leitores! Se você tiver exemplos interessantes de uso de matrizes em scripts bash, compartilhe-os.
