Prefácio
Qual Shell usar
Bash
única linguagem de script de shell que pode ser usada para arquivos executáveis.
Os scripts devem começar com #!/bin/bash
com um conjunto mínimo de sinalizadores. Use set
para definir opções de shell para que a chamada do script como bash <script_name>
não viole sua funcionalidade.
Limitar todos os scripts de shell ao bash fornece uma linguagem consistente de shell instalada em todas as nossas máquinas.
A única exceção é se você estiver limitado pelas condições para as quais está programando. Um exemplo seria os pacotes Solaris SVR4, que requerem o uso do shell Bourne usual para qualquer script.
Quando usar o Shell
O shell deve ser usado apenas para pequenos utilitários ou wrappers de script simples.
Embora o script de shell não seja uma linguagem de desenvolvimento, ele é usado para escrever vários utilitários em todo o Google. Este guia de estilo é mais um reconhecimento de seu uso, em vez de uma proposta para usá-lo em uso generalizado.
Algumas recomendações:
- Se você costuma chamar outros utilitários e faz relativamente pouca manipulação de dados, o shell é uma opção aceitável para a tarefa.
- Se o desempenho importa, use outra coisa, mas não o shell.
- Se você achar que precisa usar matrizes para mais do que atribuir
${PIPESTATUS}
, use Python. - Se você estiver escrevendo um script com mais de 100 linhas, provavelmente deverá escrevê-lo em Python. Lembre-se de que os scripts estão crescendo. Reescreva seu script em outro idioma anteriormente para evitar reescrições demoradas mais tarde.
Arquivos de shell e chamada de intérprete
Extensões de arquivo
Os arquivos executáveis não devem ter a extensão (fortemente preferida) ou a extensão .sh
. As bibliotecas devem ter a extensão .sh
e não devem ser executáveis.
Não é necessário saber em que idioma o programa foi escrito durante sua execução, e o shell não requer uma extensão, portanto, preferimos não usá-lo para arquivos executáveis.
No entanto, é importante que as bibliotecas saibam em que idioma está escrito e, às vezes, é necessário ter bibliotecas semelhantes em diferentes idiomas. Isso permite que você tenha arquivos de biblioteca com nomes idênticos com objetivos idênticos, mas escritos em idiomas diferentes devem ter o mesmo nome, exceto por um sufixo específico do idioma.
SUID / SGID
SUID e SGID são proibidos em scripts de shell.
Existem muitos problemas de segurança, tornando quase impossível fornecer proteção SUID / SGID suficiente. Embora o bash complique o lançamento do SUID, ele ainda é possível em algumas plataformas, por isso proibimos explicitamente seu uso.
Use o sudo
para acesso aprimorado, se necessário.
O meio ambiente
STDOUT vs STDERR
Todas as mensagens de erro devem ser enviadas para STDERR
.
Isso ajuda a separar o estado normal dos problemas reais.
Recomenda-se que a função de exibição de mensagens de erro seja usada junto com outras informações de status.
err() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2 } if ! do_something; then err "Unable to do_something" exit "${E_DID_NOTHING}" fi
Comentários
Cabeçalho do arquivo
Inicie cada arquivo com uma descrição do seu conteúdo.
Cada arquivo deve ter um título do comentário, incluindo uma breve descrição de seu conteúdo. Informações sobre direitos autorais e autor são opcionais.
Um exemplo:
Comentários dos Recursos
Qualquer função que não seja óbvia e curta deve ser comentada. Qualquer função na biblioteca deve ser comentada, independentemente de seu comprimento ou complexidade.
Você precisa garantir que alguém entenda como usar seu programa ou como usar a função em sua biblioteca, simplesmente lendo os comentários (e a necessidade de auto-aperfeiçoamento) sem ler o código.
Todos os comentários dos recursos devem incluir:
- Descrição da Função
- Variáveis globais usadas e modificadas
- Argumentos recebidos
- Retorne valores diferentes dos códigos de saída padrão no último comando.
Um exemplo:
Comentários de implementação
Comente partes complexas, não óbvias, interessantes ou importantes do seu código.
Presume-se que seja a prática usual de comentar código no Google. Não comente tudo. Se houver um algoritmo complicado ou você estiver fazendo algo incomum, adicione um breve comentário.
TODO Comentários
Use os comentários do TODO para códigos temporários, de curto prazo ou muito bons, mas não perfeitos.
Isso é consistente com a convenção no manual C ++ .
Os comentários do TODO devem incluir a palavra TODO em maiúsculas, seguida do seu nome entre parênteses. Dois pontos é opcional. Também é preferível indicar o número do bug / ticket próximo ao elemento TODO.
Um exemplo:
Embora você deva seguir o estilo que já é usado nos arquivos que você está editando, o seguinte é necessário para qualquer novo código.
Indentação
Recuar 2 espaços. Sem guias.
Use linhas em branco entre os blocos para melhorar a legibilidade. Recuo são dois espaços. Não importa o que você faça, não use guias. Para arquivos existentes, permaneça fiel ao recuo atual.
Comprimento da String e Comprimento do Valor
O comprimento máximo da linha é de 80 caracteres.
Se você precisar escrever linhas com mais de 80 caracteres, isso deve ser feito usando o here document
ou, se possível, a newline
. Valores literais que podem ter mais de 80 caracteres e não podem ser separados são razoavelmente permitidos, mas é altamente recomendável que você encontre uma maneira de reduzi-los.
Tubulações
Os pipelines devem ser divididos cada um em uma linha, se não couberem em uma linha.
Se um pipeline se encaixa em uma linha, ele deve estar em uma linha.
Caso contrário, ele deve ser dividido para que cada seção esteja em uma nova linha e indentada por 2 espaços para a próxima seção. Refere-se à cadeia de comandos combinada usando '|' bem como conexões lógicas usando '||' e '&&'.
Ciclos
Local ; do
; do
e ; then
; then
na mesma linha que while
, for
ou if
.
Os ciclos no shell são um pouco diferentes, mas seguimos os mesmos princípios que as chaves ao declarar funções. Isto é ; then
; then
e ; do
; do
deve estar na mesma linha que if
/ for
/ while
. else
deve estar em uma linha separada e as instruções de fechamento devem estar em sua própria linha, alinhadas verticalmente com a instrução de abertura.
Um exemplo:
for dir in ${dirs_to_cleanup}; do if [[ -d "${dir}/${ORACLE_SID}" ]]; then log_date "Cleaning up old files in ${dir}/${ORACLE_SID}" rm "${dir}/${ORACLE_SID}/"* if [[ "$?" -ne 0 ]]; then error_message fi else mkdir -p "${dir}/${ORACLE_SID}" if [[ "$?" -ne 0 ]]; then error_message fi fi done
Declaração de caso
- Opções separadas em 2 espaços.
- As opções de linha única requerem um espaço após o colchete de fechamento do modelo e antes
;;
. - Opções longas ou com vários comandos devem ser divididas em várias linhas com um modelo, ações e
;;
em linhas separadas.
Expressões correspondentes retrocedem um nível de case
e esac
. As ações multilinhas também têm recuos em um nível separado. Não há necessidade de colocar expressões entre aspas. Os padrões de expressão não devem ser precedidos por parênteses abertos. Evite usar o &;
e notação.
case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac
Comandos simples podem ser colocados em uma linha com o padrão e ;;
enquanto a expressão permanece legível. Isso geralmente é adequado para lidar com opções de letra única. Quando as ações não couberem em uma linha, deixe o modelo na sua linha; a próxima ação será ;;
também na própria linha. Quando essa é a mesma linha das ações, use um espaço após o colchete de fechamento do modelo e outro antes ;;
.
verbose='false' aflag='' bflag='' files='' while getopts 'abf:v' flag; do case "${flag}" in a) aflag='true' ;; b) bflag='true' ;; f) files="${OPTARG}" ;; v) verbose='true' ;; *) error "Unexpected option ${flag}" ;; esac done
Expansão variável
Em ordem de prioridade: observe o que já está em uso; coloque variáveis entre aspas; prefira "${var}"
mais que "$var"
, mas com atenção ao contexto de uso.
Essas são recomendações, uma vez que o tópico é bastante contraditório para a regulamentação obrigatória. Eles são listados em ordem de prioridade.
- Use o mesmo estilo que você encontra no código existente.
- Coloque variáveis entre aspas, consulte a seção Cotação abaixo.
Não coloque caracteres únicos específicos para parâmetros de shell / posicionais entre aspas e chaves, a menos que estritamente necessário e para evitar confusão profunda.
Prefira chaves para todas as outras variáveis.
Citações
- Sempre use aspas para valores que contêm variáveis, substituições de comandos, espaços ou metacaracteres de shell, até precisar expor com segurança valores que não estejam entre aspas.
- Prefira aspas para valores que são "palavras" (ao contrário de parâmetros de comando ou nomes de caminho)
- Nunca cite números inteiros.
- Saiba como as aspas funcionam para padrões de correspondência em
[[
. - Use
"$@"
se você não tiver um motivo específico para usar $*
.
Recursos e erros
Substituição de comando
Use $(command)
vez de backticks.
Os backticks aninhados requerem o escape de aspas internas com \
. O formato $ (command)
não muda dependendo do aninhamento e é mais fácil de ler.
Um exemplo:
Cheques, [
e [[
[[ ... ]]
mais preferível que [
, test
ou /usr/bin/[
.
[[ ... ]]
reduz a possibilidade de erro, porque não há resolução de caminho ou separação de palavras entre [[
e ]]
, e [[ ... ]]
permite que você use uma expressão regular onde [ ... ]
não [ ... ]
.
Verificar valores
Use aspas em vez de caracteres extras sempre que possível.
O Bash é inteligente o suficiente para trabalhar com uma string vazia em um teste. Portanto, o código resultante é muito mais fácil de ler; use verificações de valores vazios / não vazios ou valores vazios, sem usar caracteres adicionais.
Para evitar confusão sobre o que você está verificando, use explicitamente -z
ou -n
.
Expressões de substituição para nomes de arquivo
Use o caminho explícito ao criar expressões curinga para nomes de arquivos.
Como os nomes dos arquivos podem começar com o caractere -
, é muito mais seguro ./*
expressão curinga como ./*
vez de *
.
Eval
eval
deve ser evitada.
O Eval permite expandir as variáveis passadas na entrada, mas também pode definir outras variáveis, sem a possibilidade de verificá-las.
Canos em While
Use substituição de comando ou o loop for
, em vez de canalizar while
. As variáveis alteradas no while
não se propagam para o pai, porque os comandos do loop são executados em um sub-shell.
Uma subcasca implícita no canal while
pode dificultar o rastreamento de erros.
last_line='NULL' your_command | while read line; do last_line="${line}" done
Use um loop for se tiver certeza de que a entrada não conterá espaços ou caracteres especiais (geralmente isso não implica na entrada do usuário).
total=0
O uso da substituição de comando permite redirecionar a saída, mas executa comandos em um sub-shell explícito, diferente do sub-shell implícito, que cria o bash para o while
.
total=0 last_file= while read count filename; do total+="${count}" last_file="${filename}" done < <(your_command | uniq -c)
Use while
loops onde não há necessidade de passar resultados complexos para o shell pai - isso é típico quando é necessária uma "análise" mais complexa. Lembre-se de que exemplos simples às vezes são muito mais fáceis de resolver usando uma ferramenta como o awk. Também pode ser útil quando você não deseja modificar especificamente as variáveis do ambiente pai.
Convenção de nomenclatura
Nomes de Função
Minúsculas com sublinhados para separar as palavras. Separe as bibliotecas com ::
. Os colchetes são necessários após o nome da função. A palavra-chave function é opcional, mas se usada, é consistente em todo o projeto.
Se você escrever funções separadas, use minúsculas e palavras separadas com sublinhados. Se você estiver escrevendo um pacote, separe os nomes dos pacotes com ::
. Os colchetes devem estar na mesma linha que o nome da função (como em outros idiomas do Google) e não devem ter espaço entre o nome da função e o colchete.
Quando "()" vem após o nome da função, a palavra-chave da função parece redundante, mas melhora a identificação rápida de funções.
Nome da variável
Em relação aos nomes das funções.
Os nomes de variáveis para loops devem ser igualmente nomeados para qualquer variável sobre a qual você itere.
for zone in ${zones}; do something_with "${zone}" done
Nomes de constante da variável de ambiente
Todas as letras maiúsculas, separadas por sublinhados, são declaradas na parte superior do arquivo.
As constantes e tudo o que é exportado para o ambiente devem estar em maiúsculas.
Algumas coisas permanecem constantes quando são instaladas pela primeira vez (por exemplo, via getopts
). Portanto, é bastante normal definir uma constante através de getopts
ou com base em uma condição, mas isso deve ser feito readonly
após a readonly
. Observe que declare
não funciona com variáveis globais dentro de funções, portanto, readonly
ou export
recomendada.
VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE
Nomes dos arquivos de origem
Letras minúsculas, com sublinhado para separar palavras, se necessário.
Isso se aplica à correspondência com outros estilos de código no Google: maketemplate
ou make_template
, mas não o make-template
.
Variáveis somente leitura
Use readonly
ou declare -r
para garantir que sejam somente leitura.
shell, . , , .
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)" if [[ -z "${zip_version}" ]]; then error_message else readonly zip_version fi
, , local
. .
, , local
. , .
, ; local
exit code .
my_func2() { local name="$1"
. .
, . , set , .
. .
main
, main
, , .
, main
. , ( , ). main:
main "$@"
, , , main
— , .
.
$?
if
, .
:
if ! mv "${file_list}" "${dest_dir}/" ; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi
, PIPESTATUS
, - , , PIPESTATUS
( , [
PIPESTATUS
).
tar -cf - ./* | ( cd "${DIR}" && tar -xf - ) return_codes=(${PIPESTATUS[*]}) if [[ "${return_codes[0]}" -ne 0 ]]; then do_something fi if [[ "${return_codes[1]}" -ne 0 ]]; then do_something_else fi
shell , .
, bash, ( , sed
).
:
Conclusão
.
Reserve alguns minutos para ler a seção Palavras de despedida, na parte inferior do manual do C ++ .