Użyj pliku konfiguracyjnego dla mojego skryptu powłoki


26

Muszę utworzyć plik konfiguracyjny dla własnego skryptu: tutaj przykład:

scenariusz:

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

Treść /home/myuser/test/config:

nam="Mark"
sur="Brown"

to działa!

Moje pytanie: czy to jest właściwy sposób, aby to zrobić, czy istnieją inne sposoby?


Zmienne powinny znajdować się na górze. Dziwię się, że to działa. W każdym razie, dlaczego potrzebujesz pliku konfiguracyjnego? Czy planujesz użyć tych zmiennych gdzieś indziej?
— Faheem Mitha

Faheem, potrzebuję zmiennych, ponieważ mój skrypt ma wiele opcji: użycie pliku konfiguracyjnego spowoduje jego skrócenie. Dzięki
— Pol Hallen,

5
IMHO jest w porządku. Zrobiłbym w ten sposób.
— Tinti

abcderobi to również w ten sposób i jest to dość duży program (dla skryptu powłoki). Możesz to zobaczyć tutaj .
— Lucas,

Odpowiedzi:


19

sourcenie jest bezpieczny, ponieważ wykona dowolny kod. Może to nie stanowić problemu, ale jeśli uprawnienia do plików są niepoprawne, osoba atakująca z dostępem do systemu plików może wykonać kod jako użytkownik uprzywilejowany przez wstrzyknięcie kodu do pliku konfiguracyjnego załadowanego w inny sposób zabezpieczonym skryptem, takim jak skrypt inicjujący.

Jak dotąd najlepszym rozwiązaniem, jakie udało mi się zidentyfikować, jest niezdarne rozwiązanie na nowo:

myscript.conf

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

Przy użyciu sourceuruchomiłoby się to echo rm -rf /dwukrotnie, a także zmieniłoby działającego użytkownika $PROMPT_COMMAND. Zamiast tego wykonaj następujące czynności:

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 (kompatybilny z 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

Proszę odpowiedzieć, jeśli znajdziesz w moim kodzie lukę w zabezpieczeniach.


1
Do Twojej wiadomości jest to rozwiązanie Bash w wersji 4.0, które niestety podlega
— obłędnym

@Sukima Dobra uwaga. Dodałem wersję, która jest kompatybilna z Bash 3. Jego słabością jest to, że nie będzie *poprawnie obsługiwać danych wejściowych, ale co w Bash dobrze obsługuje tę postać?
— Mikkel,

Pierwszy skrypt nie powiedzie się, jeśli hasło zawiera ukośnik odwrotny.
— Kusalananda

@Kusalananda Co się stanie, jeśli ukośnik zostanie uniknięty? my\\password
— Mikkel

10

Oto czysta i przenośna wersja, która jest kompatybilna z Bash 3 i nowszymi, zarówno na Macu, jak i Linuksie.

Określa wszystkie wartości domyślne w osobnym pliku, aby uniknąć potrzeby ogromnej, zaśmieconej, zduplikowanej funkcji konfiguracyjnej „domyślne” we wszystkich skryptach powłoki. I pozwala wybierać między czytaniem z domyślnymi awariami lub bez nich:

config.cfg :

myvar=Hello World

config.cfg.defaults :

myvar=Default Value
othervar=Another Variable

config.shlib (jest to biblioteka, więc nie ma linii 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 (lub dowolne skrypty, w których chcesz odczytać wartości konfiguracji) :

#!/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

Objaśnienie skryptu testowego:

  • Zauważ, że wszystkie zastosowania config_get w test.sh są owinięte podwójnymi cudzysłowami. Zawijając każdy config_get w podwójne cudzysłowy, zapewniamy, że tekst w wartości zmiennej będzie nigdy nie będzie błędnie interpretowany jako flaga. Zapewnia to prawidłowe zachowanie białych znaków, takich jak wiele spacji w wierszu wartości konfiguracji.
  • A co to za printflinia? Cóż, jest to coś, o czym powinieneś wiedzieć: echoto złe polecenie do drukowania tekstu, nad którym nie masz kontroli. Nawet jeśli użyjesz podwójnego cudzysłowu, zinterpretuje flagi. Spróbuj ustawić myvar(in config.cfg) na, -ea zobaczysz pustą linię, ponieważ echopomyślisz, że to flaga. Ale printfnie ma tego problemu. printf --Mówi „drukuj, i niczego nie interpretować jako flagi”, a"%s\n" mówi „formatować dane wyjściowe jako ciąg z końcowym znakiem nowej linii, a wreszcie ostateczny parametr jest wartością dla printf do pliku PDF.
  • Jeśli nie zamierzasz wyświetlać wartości na ekranie, po prostu przypisujesz je normalnie, na przykład myvar="$(config_get myvar)";. Jeśli masz zamiar wydrukować je na ekranie, sugeruję użycie printf, aby całkowicie zabezpieczyć się przed ciągami niezgodnymi z echem, które mogą znajdować się w konfiguracji użytkownika. Ale echo jest w porządku, jeśli zmienna podana przez użytkownika nie jest pierwszym znakiem echa, ponieważ jest to jedyna sytuacja, w której „flagi” można interpretować, więc coś w tym rodzaju echo "foo: $(config_get myvar)";jest bezpieczne, ponieważ „foo” nie zacznij od myślnika i dlatego mówi echo, że reszta ciągu również nie jest dla niego oznaczona. :-)

@ user2993656 Dziękujemy za wykrycie, że mój oryginalny kod nadal zawierał moją prywatną nazwę pliku config (environment.cfg) zamiast poprawnego. Jeśli chodzi o edycję „echo -n”, to zależy to od użytej powłoki. W systemie Mac / Linux Bash „echo -n” oznacza „echo bez końcowego znaku nowej linii”, co zrobiłem, aby uniknąć końcowego znaku nowego wiersza. Ale wydaje się, że działa dokładnie tak samo bez niego, więc dziękuję za zmiany!
— gw0

Właściwie właśnie przeszedłem i przepisałem go, aby użyć printf zamiast echa, co gwarantuje, że pozbędziemy się ryzyka błędnej interpretacji „flag” w wartościach konfiguracji.
— gw0

Naprawdę podoba mi się ta wersja. Porzuciłem config.cfg.defaultszamiast definiowania ich w momencie dzwonienia $(config_get var_name "default_value"). tritarget.org/static/…
— Sukima

7

Analizuj plik konfiguracyjny, nie wykonuj go.

Obecnie piszę w pracy aplikację, która używa niezwykle prostej konfiguracji XML:

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

W skrypcie powłoki („aplikacji”) robię to, aby uzyskać nazwę użytkownika (mniej więcej umieściłem ją w funkcji powłoki):

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

xmlKomenda jest XMLStarlet , który jest dostępny dla większości Uniksów.

Używam XML, ponieważ inne części aplikacji dotyczą również danych zakodowanych w plikach XML, więc było to najłatwiejsze.

Jeśli wolisz JSON, jest jq łatwy w użyciu parser JSON powłoki.

Mój plik konfiguracyjny wyglądałby mniej więcej w JSON:

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

A potem dostałbym nazwę użytkownika w skrypcie:

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

Wykonywanie skryptu ma wiele zalet i wad. Główną wadą jest bezpieczeństwo, jeśli ktoś może zmodyfikować plik konfiguracyjny, wówczas może wykonać kod, a trudniej jest uczynić go idiotycznym. Zaletą jest szybkość, w prostym teście jest ponad 10 000 razy szybsze źródło pliku konfiguracyjnego niż uruchomienie pq, i elastyczność, każdy, kto lubi pythonowe łatanie małp, doceni to.
— icarus

@icarus Jak często spotykane są pliki konfiguracyjne i jak często trzeba je analizować w jednej sesji? Zauważ też, że za jednym razem można uzyskać kilka wartości z XML lub JSON.
— Kusalananda

Zwykle tylko kilka (1 do 3) wartości. Jeśli używasz evaldo ustawiania wielu wartości, wówczas wykonujesz wybrane części pliku konfiguracyjnego :-).
— icarus

1
@icarus Myślałem o tablicach ... Nie trzeba evalniczego. Wydajność wynikająca z używania standardowego formatu z istniejącym parserem (nawet jeśli jest to narzędzie zewnętrzne) jest nieznaczna w porównaniu z odpornością, ilością kodu, łatwością użycia i łatwością konserwacji.
— Kusalananda

1
+1 za „parsowanie pliku konfiguracyjnego, nie uruchamiaj go”
— Iiridayn

4

Najczęstszym, wydajnym i poprawnym sposobem jest użycie sourcelub .forma skrócona. Na przykład:

source /home/myuser/test/config

lub

. /home/myuser/test/config

Należy jednak wziąć pod uwagę problemy związane z bezpieczeństwem, które może spowodować użycie dodatkowego pliku konfiguracyjnego pochodzącego z zewnątrz, ponieważ można wstawić dodatkowy kod. Aby uzyskać więcej informacji, w tym na temat wykrywania i rozwiązywania tego problemu, polecam zajrzeć do sekcji „Zabezpiecz to” na http://wiki.bash-hackers.org/howto/conffile#secure_it


5
Miałem duże nadzieje na ten artykuł (pojawił się także w moich wynikach wyszukiwania), ale sugestia autora dotycząca próby użycia wyrażenia regularnego w celu odfiltrowania złośliwego kodu jest ćwiczeniem daremnym.
— Mikkel

Procedura z kropką, wymaga bezwzględnej ścieżki? Z tym względnym nie działa
— Davide

2

Używam tego w moich skryptach:

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
}

Powinien obsługiwać każdą kombinację znaków, z wyjątkiem kluczy, których nie można =w nich mieć , ponieważ jest to separator. Wszystko inne działa.

% 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

Jest to również całkowicie bezpieczne, ponieważ nie korzysta z żadnego rodzaju source/eval


0

W moim scenariuszu sourcelub .było w porządku, ale chciałem wspierać lokalne zmienne środowiskowe (tj. FOO=bar myscript.sh), Które mają pierwszeństwo przed zmiennymi skonfigurowanymi. Chciałem też, aby plik konfiguracyjny był edytowalny przez użytkownika i wygodny dla kogoś, kto zwykł pozyskiwać pliki konfiguracyjne, i aby był tak mały / prosty, jak to możliwe, aby nie odwracał uwagi od głównego ciągu mojego bardzo małego skryptu.

Oto co wymyśliłem:

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)

Zasadniczo - sprawdza definicje zmiennych (bez zbytniej elastyczności co do białych znaków) i przepisuje te wiersze, aby wartość została przekonwertowana na wartość domyślną dla tej zmiennej, a zmienna nie jest modyfikowana, jeśli zostanie znaleziona, tak jak XDG_CONFIG_HOME zmienna powyżej. Źródła tej zmienionej wersji pliku konfiguracyjnego i kontynuuje.

Przyszłe prace mogą sprawić, że sedskrypt będzie bardziej niezawodny, odfiltrować wiersze, które wyglądają dziwnie lub nie są definicjami itp., Nie przerywają komentarzy na końcu wiersza - ale na razie jest to dla mnie wystarczające.


0

Jest to zwięzłe i bezpieczne:

# 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`)

Te -i, zapewnia można dostać tylko od zmiennychcommon.vars


-2

Możesz to zrobić:

#!/bin/bash
name="mohsen"
age=35
cat > /home/myuser/test/config << EOF
Name=$name
Age=$age
EOF
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.