Use o arquivo de configuração para meu script de shell

Eu preciso criar um arquivo de configuração para meu próprio script:
Aqui está um exemplo:

script:

#!/bin/bash source /home/myuser/test/config echo "Name=$nam" >&2 echo "Surname=$sur" >&2 

Conteúdo de /home/myuser/test/config:

nam="Mark" sur="Brown" 

isso funciona!

Minha pergunta: esta é a maneira correta de faça isso ou há outras maneiras?

Comentários

  • As variáveis devem estar no topo. I ‘ estou surpreso que funcione. De qualquer forma, por que você precisa de um arquivo de configuração? Você planeja usar essas variáveis em outro lugar?
  • Faheem, preciso das variáveis porque meu script tem muitas opções: usar um arquivo de configuração semplificará o script. Obrigado
  • IMHO, tudo bem. Eu faria desta forma.
  • abcde também faz desta forma e esse é um programa bastante grande (para um script de shell). Você pode dar uma olhada nele aqui .

Resposta

source não é seguro, pois executa código arbitrário. Isso pode não ser uma preocupação para você, mas se as permissões de arquivo estiverem incorretas, pode ser possível para um invasor com acesso ao sistema de arquivos executar o código como um usuário privilegiado, injetando código em um arquivo de configuração carregado por um script protegido de outra forma, como um script de inicialização.

Até agora, a melhor solução que consegui identificar é a desajeitada solução reinventando a roda:

myscript.conf

password=bar echo rm -rf / PROMPT_COMMAND="echo "Sending your last command $(history 1) to my email"" hostname=localhost; echo rm -rf / 

Usando source, isso executaria echo rm -rf / duas vezes, bem como alteraria o usuário em execução “s $PROMPT_COMMAND. Em vez disso, faça o seguinte:

myscript.sh (Bash 4)

#!/bin/bash typeset -A config # init array config=( # set default values in config array [username]="root" [password]="" [hostname]="localhost" ) while read line do if echo $line | grep -F = &>/dev/null then varname=$(echo "$line" | cut -d "=" -f 1) config[$varname]=$(echo "$line" | cut -d "=" -f 2-) fi done < myscript.conf echo ${config[username]} # should be loaded from defaults echo ${config[password]} # should be loaded from config file echo ${config[hostname]} # includes the "injected" code, but it"s fine here echo ${config[PROMPT_COMMAND]} # also respects variables that you may not have # been looking for, but they"re sandboxed inside the $config array 

myscript.sh (compatível com Mac / Bash 3)

#!/bin/bash config() { val=$(grep -E "^$1=" myscript.conf 2>/dev/null || echo "$1=__DEFAULT__" | head -n 1 | cut -d "=" -f 2-) if [[ $val == __DEFAULT__ ]] then case $1 in username) echo -n "root" ;; password) echo -n "" ;; hostname) echo -n "localhost" ;; esac else echo -n $val fi } echo $(config username) # should be loaded from defaults echo $(config password) # should be loaded from config file echo $(config hostname) # includes the "injected" code, but it"s fine here echo $(config PROMPT_COMMAND) # also respects variables that you may not have # been looking for, but they"re sandboxed inside the $config array 

Por favor, responda se você encontrar uma falha de segurança em meu código.

Comentários

  • Para sua informação, esta é uma solução Bash versão 4.0 que infelizmente está sujeita a problemas insanos de licenciamento impostos pela Apple e não está disponível por padrão em Macs
  • @Sukima Bom ponto. Eu ‘ adicionei uma versão compatível com o Bash 3. Seu ponto fraco é que ele não lida com * nas entradas de maneira adequada, mas então o que no Bash lida bem com esse caractere?
  • O primeiro script falha se a senha contém uma barra invertida.
  • @Kusalananda E se a barra invertida tem escape? my\\password
  • Esta versão leva vários segundos para processar meu arquivo de configuração, descobri que a solução do gw0 é muito mais rápida.

Resposta

Analise o arquivo de configuração, não execute-o.

No momento, estou escrevendo um aplicativo de trabalho que usa uma configuração XML extremamente simples:

<config> <username>username-or-email</username> <password>the-password</password> </config> 

No script de shell (o “aplicativo”), isso é o que eu faço para obter o nome de usuário (mais ou menos, eu coloquei em uma função shell):

username="$( xml sel -t -v "/config/username" "$config_file" )" 

O comando xml é XMLStarlet , que está disponível para a maioria dos Unices.

Estou usando XML, pois outras partes do aplicativo também lidam com dados codificados em arquivos XML, então foi mais fácil.

Se você preferir JSON, há “s jq que é um analisador JSON shell fácil de usar.

Meu arquivo de configuração seria parecido com isto em JS ATIVADO:

{ "username": "username-or-email", "password": "the-password" } 

E então eu “estaria recebendo o nome de usuário no script:

username="$( jq -r ".username" "$config_file" )" 

Comentários

  • Executar o script tem várias vantagens e desvantagens. As principais desvantagens são a segurança, se alguém pode alterar o arquivo de configuração, ele pode executar o código, e é mais difícil torná-lo à prova de idiotas. As vantagens são a velocidade, em um teste simples é mais de 10.000 vezes mais rápido criar um arquivo de configuração do que executar pq, e flexibilidade, qualquer pessoa que goste de fazer patch de macaco em python vai gostar disso.
  • @icarus Quão grande arquivos de configuração que você geralmente encontra e com que frequência você precisa analisá-los em uma sessão? Observe também que vários valores podem ser obtidos de XML ou JSON de uma vez.
  • Normalmente, apenas alguns valores (1 a 3). Se você estiver usando eval para definir vários valores, você está executando partes selecionadas do arquivo de configuração :-).
  • @icarus Eu estava pensando em matrizes … Não há necessidade de eval nada. O impacto no desempenho de usar um formato padrão com um analisador existente (embora seja ‘ s um utilitário externo) é insignificante em comparação com a robustez, a quantidade de código, a facilidade de uso e facilidade de manutenção.
  • +1 para ” analise o arquivo de configuração, não ‘ para executá-lo ”

Resposta

Aqui está uma versão limpa e portátil que é compatível com Bash 3 e , no Mac e no Linux.

Ele especifica todos os padrões em um arquivo separado, para evitar a necessidade de uma função de configuração “padrões” duplicada e desordenada em todos os seus scripts de shell. E permite que você escolha entre ler com ou sem substitutos padrão:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (esta é uma biblioteca, então não há shebang-line):

config_read_file() { (grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d "=" -f 2-; } config_get() { val="$(config_read_file config.cfg "${1}")"; if [ "${val}" = "__UNDEFINED__" ]; then val="$(config_read_file config.cfg.defaults "${1}")"; fi printf -- "%s" "${val}"; } 

test.sh (ou qualquer script onde você quiser leia os valores de configuração) :

#!/usr/bin/env bash source config.shlib; # load the config library functions echo "$(config_get myvar)"; # will be found in user-cfg printf -- "%s\n" "$(config_get myvar)"; # safer way of echoing! myvar="$(config_get myvar)"; # how to just read a value without echoing echo "$(config_get othervar)"; # will fall back to defaults echo "$(config_get bleh)"; # "__UNDEFINED__" since it isn"t set anywhere 

Explicação do script de teste:

  • Observe que todos os usos de config_get em test.sh estão entre aspas duplas. Envolvendo cada config_get entre aspas duplas, garantimos que o texto no valor da variável nunca será mal interpretado como sinalizadores. E garante que preservemos os espaços em branco adequadamente, como vários espaços em uma linha no valor de configuração.
  • E o que é essa printf linha? Bem, é “É algo que você deve saber: echo é um comando incorreto para imprimir texto sobre o qual você não tem controle. Mesmo se você usar aspas duplas, ele interpretará sinalizadores. Tente definir myvar (em config.cfg) como -e e você verá uma linha vazia, porque echo pensará que é “um sinalizador. Mas printf não tem esse problema. O printf -- diz “imprima isto e não interprete nada como sinalizadores” e o "%s\n" diz “formate a saída como uma string com uma nova linha à direita e, por último, o parâmetro final é o valor para printf formatar.
  • Se você não vai exibir valores para a tela, basta atribuí-los normalmente, como myvar="$(config_get myvar)";. Se você for imprimi-los na tela, sugiro usar printf para ficar totalmente seguro contra quaisquer strings incompatíveis com eco que possam estar na configuração do usuário. Mas echo está bem se a variável fornecida pelo usuário não é o primeiro caractere da string que você está ecoando, uma vez que essa “é a única situação em que” sinalizadores “podem ser interpretados, então algo como echo "foo: $(config_get myvar)"; é seguro, já que” foo “não” começa com um travessão e, portanto, diz à echo que o resto da string também não é sinalizada. 🙂

Comentários

  • @ user2993656 Obrigado por notar que meu código original ainda tinha meu nome de arquivo de configuração privado (environment.cfg) em vez do correto. Quanto ao ” echo -n ” edição que você fez, que depende do shell usado. No Mac / Linux Bash, ” echo -n ” significa ” eco sem nova linha final “, que fiz para evitar novas linhas à direita. Mas parece funcionar exatamente da mesma forma sem ele, então obrigado pelas edições!
  • Na verdade, eu apenas o reescrevi para usar printf em vez de echo, o que garante que ‘ Vou livrar-se do risco de interpretar incorretamente o eco ” flags ” nos valores de configuração.
  • Gosto muito dessa versão. Abandonei o config.cfg.defaults em vez de defini-lo no momento de chamar $(config_get var_name "default_value"). tritarget.org/static/…
  • Da mesma forma – isso é ótimo.

Resposta

A maneira mais comum, eficiente e correta é usar source ou . como uma forma abreviada. Por exemplo:

source /home/myuser/test/config 

ou

. /home/myuser/test/config 

Algo a se considerar, no entanto, é o problemas de segurança que podem surgir com o uso de um arquivo de configuração adicional de origem externa, visto que código adicional pode ser inserido. Para obter mais informações, inclusive sobre como detectar e resolver esse problema, recomendo dar uma olhada na seção “Proteger” de http://wiki.bash-hackers.org/howto/conffile#secure_it

Comentários

  • Eu tinha grandes esperanças para esse artigo (apareceu em meus resultados de pesquisa também), mas o autor ‘ A sugestão de tentar usar regex para filtrar código malicioso é um exercício de futilidade.
  • O procedimento com ponto requer um caminho absoluto?Com o relativo, não ‘ não funciona

Resposta

Eu uso isso em meus scripts:

sed_escape() { sed -e "s/[]\/$*.^[]/\\&/g" } cfg_write() { # path, key, value cfg_delete "$1" "$2" echo "$2=$3" >> "$1" } cfg_read() { # path, key -> value test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1 } cfg_delete() { # path, key test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1" } cfg_haskey() { # path, key test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null } 

Deve suportar todas as combinações de caracteres, exceto as chaves não podem “ter = neles, já que esse é o separador. Qualquer outra coisa funciona.

% cfg_write test.conf mykey myvalue % cfg_read test.conf mykey myvalue % cfg_delete test.conf mykey % cfg_haskey test.conf mykey || echo "It"s not here anymore" It"s not here anymore 

Além disso, é totalmente seguro, pois não usa source ou eval.

Comentários

  • Não ‘ isso supostamente para criar primeiro o arquivo de configuração se ele não ‘ existir? Não ‘ t, mas touch -a "${path}" irá, é claro, garantir que exista, também sem atualizar frivolamente seu mtime.

Resposta

Este é sucinto e seguro:

# Read common vars from common.vars # the incantation here ensures (by env) that only key=value pairs are present # then declare-ing the result puts those vars in our environment declare $(env -i `cat common.vars`) 

O -i garante que você obtenha apenas as variáveis de common.vars

Atualização: Uma ilustração da segurança é que

env -i "touch evil1 foo=omg boo=$(touch evil2)" 

Não produzirá nenhum arquivo tocado. Testado em mac com bash, ou seja, usando bsd env.

Comentários

  • Apenas veja como os arquivos evil1 e evil2 são criados se você colocar isso em comm on.vars “ `touch evil1 foo = omg boo = $ (touch evil2)` “
  • @pihentagy Para mim, o seguinte não produz arquivos tocados env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Executando no mac.
  • de fato, mas não pode acessar foo. Eu ‘ tentei env -i ... myscript.sh e dentro desse script foo não está definido. No entanto, se você remover ” lixo “, ele funcionará. Obrigado por explicar. : +1:

Resposta

A maioria dos usuários, embora não muitos contêineres, já tem o git binário. Por que não usar git config para gerenciamento de configuração de aplicativo usando um arquivo de configuração não conflitante dedicado como nos exemplos abaixo?

# Set $ git config -f ~/.myapp core.mykey myval # Get $ git config -f ~/.myapp core.mykey myval # Get invalid $ git config -f ~/.myapp core.mykey $ echo $? 1 # List git config -f ~/.myapp -l core.mykey=myval # View $ cat ~/.myapp [core] mykey = myval 

Para comandos adicionais, consulte seu man página. É aconselhável primeiro garantir que o arquivo de configuração exista:

touch -a ~/.myapp 

Resposta

Este parece seguro e curto. Sinta-se à vontade para quebrar isso implacavelmente. Eu gostaria de saber uma maneira melhor.

TL; DR;

while read LINE; do declare "$LINE"; done < evil.conf 

Estou usando o bash 4.3.48.

Também é compatível com bash --posix. Consulte o final do teste.

Mas sh não é compatível por causa de declare.


Teste básico para quem deseja prova

Crie o arquivo evil.conf

 echo > evil.conf " A=1 B=2 C=$(echo hello) # Could produce side-effect D=`touch evil` C=$((1+2)) E=$(ping 8.8.8.8 -n 3) echo hello # Could produce visible side-effect touch evil2 ping 8.8.8.8 -n 3 F=ok"  

Carregue a configuração com o snippet

 while read LINE; do declare "$LINE"; done < evil.conf  

Saída (veja o sanitizer em ação)

bash: declare: `": not a valid identifier bash: declare: `": not a valid identifier bash: declare: `# Could produce side-effect": not a valid identifier bash: declare: `echo hello": not a valid identifier bash: declare: `": not a valid identifier bash: declare: `# Could produce visible side-effect": not a valid identifier bash: declare: `touch evil2": not a valid identifier bash: declare: `ping 8.8.8.8 -n 3": not a valid identifier 

Vamos verificar os valores agora

 for V in A B C D E F; do declare -p $V; done  
declare -- A="1" declare -- B="2" declare -- C="\$((1+2))" declare -- D="\`touch evil\`" declare -- E="\$(ping 8.8.8.8 -n 3)" declare -- F="ok" 

Verifique os efeitos colaterais (sem efeitos colaterais):

 ls evil evil2  
ls: cannot access "evil": No such file or directory ls: cannot access "evil2": No such file or directory 

Apêndice. Teste bash --posix

bash -c "while read LINE; do declare "$LINE"; done < evil.conf; for V in A B C D E F; do declare -p $V; done" --posix 

Resposta

Para meu cenário, source ou . w bem, mas eu queria oferecer suporte a variáveis de ambiente locais (ou seja, FOO=bar myscript.sh) tendo precedência sobre as variáveis configuradas. Eu também queria que o arquivo de configuração fosse editável pelo usuário e confortável para alguém acostumado a arquivos de configuração de origem e que o mantivesse o mais pequeno / simples possível, para não desviar o foco do meu script muito pequeno. > Isto é o que eu fiz:

CONF=${XDG_CONFIG_HOME:-~/config}/myscript.sh if [ ! -f $CONF ]; then cat > $CONF << CONF VAR1="default value" CONF fi . <(sed "s/^\([^=]\+\) *= *\(.*\)$/\1=${\1:-\2}/" < $CONF) 

Essencialmente – ele verifica as definições de variáveis (sem ser muito flexível quanto aos espaços em branco) e reescreve essas linhas para que o o valor é convertido para um padrão para essa variável e a variável não é modificada se encontrada, como a variável XDG_CONFIG_HOME acima. Ele fornece essa versão alterada do arquivo de configuração e continua.

Trabalhos futuros podem tornar o script sed mais robusto, filtrar linhas que parecem estranhas ou não ” definições t, etc, não interrompem comentários de fim de linha – mas isso é bom o suficiente para mim por enquanto.

Resposta

Você pode fazer isso:

#!/bin/bash name="mohsen" age=35 cat > /home/myuser/test/config << EOF Name=$name Age=$age EOF 

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *