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

22

Eu preciso criar um arquivo de configuração para o meu próprio script: aqui 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 fazer isso ou existem outras maneiras?

    
por Pol Hallen 23.12.2014 / 17:55

7 respostas

17

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

Até agora, a melhor solução que consegui identificar é a desajeitada solução de reinventar 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, além de alterar o usuário em execução $PROMPT_COMMAND . Em vez disso, faça isso:

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 exploração de segurança no meu código.

    
por 29.05.2015 / 01:36
9

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

Ele especifica todos os padrões em um arquivo separado, para evitar a necessidade de uma função de configuração "defaults" enorme, desordenada e duplicada em todos os seus scripts de shell. E permite escolher entre ler com ou sem fallbacks padrão:

config.cfg :

myvar=Hello World

config.cfg.defaults :

myvar=Default Value
othervar=Another Variable

config.shlib (isto é uma biblioteca, então não há linha de shebang):

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 em que você queira ler 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:

  • Note que todos os usos do config_get no test.sh estão agrupados em aspas duplas. Envolvendo cada config_get entre aspas, garantimos que o texto no valor da variável nunca seja interpretado como sinalizadores. E garante que preservemos o espaço em branco corretamente, como vários espaços em uma linha no valor de configuração.
  • E o que é isso printf line? Bem, é algo que você deve estar ciente: echo é um mau comando para imprimir texto sobre o qual você não tem controle. Mesmo se você usar aspas duplas, ele interpretará as flags. Tente definir myvar (em config.cfg ) como -e e você verá uma linha vazia, porque echo pensará que é uma bandeira. Mas printf não tem esse problema. O printf -- diz "imprima isso e não interprete nada como sinalizadores", e o "%s\n" diz "formata a saída como uma string com uma nova linha à direita, e por último o parâmetro final é o valor para printf para formatar.
  • Se você não estiver exibindo valores para a tela, simplesmente os atribua normalmente, como myvar="$(config_get myvar)"; . Se você vai imprimi-los na tela, sugiro usar o printf para ser totalmente seguro contra quaisquer strings incompatíveis com o eco que possam estar na configuração do usuário. Mas o echo é bom se a variável fornecida pelo usuário não for o primeiro caractere da string que você está ecoando, já que é a única situação em que "flags" pode ser interpretado, então algo como echo "foo: $(config_get myvar)"; é seguro, desde o "foo" não começa com um traço e, portanto, diz eco que o resto da string não é sinalizadores para ele também. : -)
por 21.12.2016 / 17:09
6

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

Atualmente, estou escrevendo um aplicativo no trabalho que usa uma configuração XML extremamente simples:

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

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

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

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

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

Se você preferir o JSON, jq é um analisador de shell JSON fácil de usar.

Meu arquivo de configuração seria algo parecido com isso no JSON:

{                                 
  "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" )"
    
por 04.01.2017 / 20:16
4

A maneira mais comum, eficiente e correta é usar source ou . como formato abreviado. Por exemplo:

source /home/myuser/test/config

ou

. /home/myuser/test/config

Algo a considerar, no entanto, são os problemas de segurança que o uso de um arquivo de configuração externo de origem adicional pode gerar, já que códigos adicionais podem ser inseridos. Para obter mais informações, inclusive sobre como detectar e resolver esse problema, recomendo dar uma olhada na seção "Proteger" de link

    
por 24.12.2014 / 09:17
1

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 que as chaves não podem ter = , 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, isso é totalmente seguro, pois não usa nenhum tipo de source / eval

    
por 27.03.2018 / 13:49
0

Para meu cenário, source ou . estava bom, mas eu queria suportar variáveis de ambiente locais (ou seja, FOO=bar myscript.sh ) tomando precedência sobre as variáveis configuradas. Eu também queria que o arquivo de configuração fosse de usuário editável e confortável para alguém acostumado a arquivos de configuração, e para mantê-lo o mais pequeno / simples possível, para não distrair do impulso principal do meu script muito pequeno.

Isso é o que eu criei:

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

Essencialmente - verifica as definições das variáveis (sem ser muito flexível sobre os espaços em branco) e reescreve essas linhas para que o valor seja convertido em 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 origina essa versão alterada do arquivo de configuração e continua.

O trabalho futuro poderia tornar o script sed mais robusto, filtrar as linhas que parecem estranhas ou não são definições, etc, e não quebrar os comentários finais - mas isso é bom o suficiente para mim por enquanto.

    
por 04.07.2017 / 02:31
-2

Você pode fazer isso:

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