Użyj pliku konfiguracyjnego dla mojego skryptu powłoki

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

skrypt:

#!/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 zrobić to czy istnieją „są inne sposoby?

Komentarze

  • Zmienne powinny znajdować się na górze. I ' m zaskoczony, że to działa. W każdym razie, po co potrzebny jest plik konfiguracyjny? Czy planujesz użyć tych zmiennych gdzie indziej?
  • Faheem, potrzebuję zmiennych, ponieważ mój skrypt ma wiele opcji: używanie config semplifikuje skrypt. Dzięki
  • IMHO wszystko w porządku. Zrobiłbym to w ten sposób.
  • abcde również robi to w ten sposób i to jest dość duży program (jak na skrypt powłoki). Możesz go obejrzeć tutaj .

Odpowiedź

source nie jest bezpieczna, ponieważ wykona dowolny kod. Może to nie być dla ciebie problemem, ale jeśli uprawnienia do pliku są niepoprawne, osoba atakująca z dostępem do systemu plików może wykonać kod jako uprzywilejowany użytkownik, wstrzykując kod do pliku konfiguracyjnego załadowanego przez zabezpieczony w inny sposób skrypt, taki jak skryptu startowego.

Jak dotąd najlepszym rozwiązaniem, jakie udało mi się zidentyfikować, jest niezdarne rozwiązanie polegające na ponownym wynalezieniu koła:

myscript.conf

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

Używając source, spowodowałoby to dwukrotne uruchomienie echo rm -rf /, a także zmianę uruchomionych użytkowników $PROMPT_COMMAND. Zamiast tego zrób to:

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 

Odpowiedz, jeśli znajdziesz lukę w zabezpieczeniach w moim kodzie.

Komentarze

  • FYI to jest rozwiązanie Bash w wersji 4.0, które niestety podlega szalonym problemom licencyjnym narzuconym przez Apple i nie jest domyślnie dostępne na komputerach Mac.
  • @Sukima Słuszna uwaga. ' dodałem wersję, która jest zgodna z Bash 3. Jej wadą jest to, że nie obsługuje on prawidłowo * danych wejściowych, ale co w Bash dobrze obsługuje ten znak?
  • Pierwszy skrypt kończy się niepowodzeniem, jeśli hasło zawiera ukośnik odwrotny.
  • @Kusalananda A co, jeśli znak ukośnika odwrotnego zostanie zmieniony? my\\password
  • Przetwarzanie mojego pliku konfiguracyjnego w tej wersji zajmuje kilka sekund. Okazało się, że rozwiązanie z gw0 jest znacznie szybsze.

Odpowiedź

Przetwórz plik konfiguracyjny, nie wykonuj go.

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

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

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

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

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

Używam XML, ponieważ inne części aplikacji również obsługują dane zakodowane w plikach XML, więc to było najłatwiejsze.

Jeśli wolisz JSON, istnieje „ jq , który jest łatwy w użyciu parser powłoki JSON.

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

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

A potem otrzymam nazwę użytkownika w skrypcie:

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

Komentarze

  • Wykonanie skryptu ma wiele zalet i wad. Głównymi wadami są bezpieczeństwo, jeśli ktoś może zmienić plik konfiguracyjny, może wykonać kod, a trudniej jest uczynić go idiotycznym. Zaletą jest szybkość, w prostym teście pobranie pliku konfiguracyjnego jest ponad 10 000 razy szybsze niż uruchomienie pq, a także elastyczność, każdy, kto lubi małpa łatać Pythona, doceni to.
  • @icarus Jak duże pliki konfiguracyjne, z którymi zwykle się spotykasz i jak często musisz je analizować w jednej sesji? Zauważ też, że kilka wartości może pochodzić z XML lub JSON za jednym razem.
  • Zwykle tylko kilka (od 1 do 3) wartości. Jeśli używasz eval do ustawiania wielu wartości, to wykonujesz wybrane części pliku konfiguracyjnego :-).
  • @icarus Myślałem o tablicach … Nie musisz niczego eval. Wydajność korzystania ze standardowego formatu z istniejącym parserem (mimo że ' jest zewnętrznym narzędziem) jest pomijalna w porównaniu z wytrzymałością, ilością kodu, łatwością użycia i łatwość konserwacji.
  • +1 dla ” przeanalizuj plik konfiguracyjny, nie ', nie wykonuj go ”

Odpowiedź

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

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

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (to jest 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 przeczytaj wartości konfiguracyjne) :

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

Wyjaśnienie skryptu testowego:

  • Zauważ, że wszystkie użycia config_get w test.sh są zapakowane w podwójne cudzysłowy. Zawijając każdy config_get w podwójne cudzysłowy, zapewniamy, że tekst w wartości zmiennej nigdy nie zostanie błędnie zinterpretowany jako flagi. Zapewnia też prawidłowe zachowanie białych znaków, takich jak wiele spacji w wierszu w wartości konfiguracyjnej.
  • A co to za wiersz printf? „to coś, o czym powinieneś wiedzieć: echo to złe polecenie do drukowania tekstu, nad którym nie masz kontroli. Nawet jeśli użyjesz podwójnych cudzysłowów, zinterpretuje flagi. Spróbuj ustawić myvar (w config.cfg) na -e, a zobaczysz pusty wiersz, ponieważ echo pomyśli, że to „flaga”. Ale printf nie ma tego problemu. printf -- mówi „drukuj to i nie interpretuj niczego jako flagi”, a "%s\n" mówi: „sformatuj wynik jako ciąg z końcowym znakiem nowej linii, a ostatnim parametrem jest wartość, którą printf ma sformatować.
  • Jeśli nie chcesz wyświetlać na ekranie wartości, to po prostu przypisujesz je normalnie, na przykład myvar="$(config_get myvar)";. Jeśli zamierzasz wydrukować je na ekranie, sugeruję użycie printf, aby całkowicie zabezpieczyć się przed wszelkimi niekompatybilnymi z echo ciągami, które mogą znajdować się w konfiguracji użytkownika. pierwszy znak ciągu, który powtarzasz, ponieważ jest to jedyna sytuacja, w której można zinterpretować „flagi”, więc coś takiego jak echo "foo: $(config_get myvar)"; jest bezpieczne, ponieważ „ foo „nie” nie zaczyna się od myślnika i dlatego mówi echo, że reszta łańcucha również nie jest flagą. 🙂

Komentarze

  • @ user2993656 Dziękuję za wykrycie, że mój oryginalny kod nadal zawierał moją prywatną nazwę pliku konfiguracyjnego (environment.cfg) zamiast prawidłowej. Jeśli chodzi o ” echo -n ” dokonałeś edycji, która zależy od używanej powłoki. W systemie Mac / Linux Bash, ” echo -n ” oznacza ” echo bez końcowego znaku nowej linii „, co zrobiłem, aby uniknąć końcowych znaków nowej linii. Ale wydaje się, że bez niego działa dokładnie tak samo, więc dziękuję za zmiany!
  • Właściwie to właśnie przejrzałem i przepisałem, aby używał printf zamiast echo, co zapewnia, że ' pozbędziemy się ryzyka błędnej interpretacji echa ” flag ” w wartościach konfiguracyjnych.
  • Bardzo podoba mi się ta wersja. Porzuciłem config.cfg.defaults zamiast definiować je w momencie wywoływania $(config_get var_name "default_value"). tritarget.org/static/…
  • Podobnie – to świetnie.

Odpowiedź

Najczęstszym, skutecznym i prawidłowym sposobem jest użycie source lub . w formie skróconej. Na przykład:

source /home/myuser/test/config 

lub

. /home/myuser/test/config 

Należy jednak wziąć pod uwagę problemy z bezpieczeństwem, które mogą wywołać użycie dodatkowego pliku konfiguracyjnego pochodzącego z zewnątrz, biorąc pod uwagę, że można wstawić dodatkowy kod. Aby uzyskać więcej informacji, w tym o tym, jak wykryć i rozwiązać ten problem, zajrzyj do sekcji „Secure it” w http://wiki.bash-hackers.org/howto/conffile#secure_it

Komentarze

  • Z tym artykułem wiązałem duże nadzieje (pojawił się również w moich wynikach wyszukiwania), ale autor ' sugestia próby użycia wyrażenia regularnego do odfiltrowania złośliwego kodu jest daremna.
  • Procedura z kropką wymaga bezwzględnej ścieżki?W przypadku tego względnego nie ' nie działa

Odpowiedź

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óre nie mogą mieć = w nich, ponieważ to jest 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 

Ponadto jest to całkowicie bezpieczne, ponieważ nie używa source ani eval.

Komentarze

  • Isn ' to przypuszczalnie aby najpierw utworzyć plik konfiguracyjny, jeśli ' nie istnieje? Nie ' t, ale touch -a "${path}" oczywiście upewni się, że istnieje, również bez niepoważnej aktualizacji czasu mtime.

Odpowiedź

To jest zwięzły i bezpieczny:

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

-i zapewnia, że otrzymujesz zmienne tylko z common.vars

Aktualizacja: Ilustracja bezpieczeństwa jest taka, że

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

Nie tworzy żadnych dotkniętych plików. Przetestowano na komputerze Mac z bash, czyli używając bsd env.

Komentarze

  • Po prostu zobacz, jak tworzone są pliki evil1 i evil2, jeśli umieścisz to w comm on.vars „ touch evil1 foo = omg boo = $ (touch evil2) „
  • @pihentagy Dla mnie poniższe nie tworzy żadnych dotkniętych plików env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Działa na Macu.
  • Rzeczywiście, ale nie ma dostępu do foo. ' próbowałem env -i ... myscript.sh i wewnątrz tego skryptu foo nie jest zdefiniowane. Jeśli jednak usuniesz ” śmieci „, to zadziała. Dziękuję za wyjaśnienie. : +1:

Odpowiedź

Większość użytkowników, chociaż nie ma wielu kontenerów, ma już binarny. Dlaczego więc nie użyć git config do zarządzania konfiguracją aplikacji przy użyciu dedykowanego, nie powodującego konfliktu pliku konfiguracyjnego, jak w poniższych przykładach?

# 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 

Dodatkowe polecenia można znaleźć w podręczniku strona. Dobrze jest najpierw upewnić się, że plik konfiguracyjny istnieje:

touch -a ~/.myapp 

Odpowiedź

Ten wydaje się bezpieczny i krótki. Nie krępuj się tego bezlitośnie. Chciałbym poznać lepszy sposób.

TL; DR;

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

Używam basha 4.3.48.

Jest również zgodny z bash --posix. Zobacz na dole do testu.

Ale sh nie obsługuje go z powodu declare.


Podstawowy test dla tych, którzy chcą dowodu

Utwórz plik 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"  

Załaduj konfigurację z fragmentem kodu

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

Wyjście (zobacz działanie odkażacza)

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 

Sprawdźmy teraz wartości

 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" 

Sprawdź efekty uboczne (brak efektów ubocznych):

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

Dodatek. Test 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 

Odpowiedź

W moim scenariuszu source lub . w tak dobrze, ale chciałem obsługiwać lokalne zmienne środowiskowe (tj. FOO=bar myscript.sh) mające pierwszeństwo przed zmiennymi skonfigurowanymi. Chciałem również, aby plik konfiguracyjny był edytowalny przez użytkownika i wygodny dla kogoś przyzwyczajonego do pozyskiwania plików konfiguracyjnych oraz aby był tak mały / prosty, jak to tylko możliwe, aby nie odciągał uwagi od głównego celu 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 w kwestii białych znaków) i przepisuje te wiersze, aby wartość jest konwertowana na wartość domyślną dla tej zmiennej, a zmienna jest niezmodyfikowana, jeśli zostanie znaleziona, na przykład zmienna XDG_CONFIG_HOME powyżej. Źródła tej zmienionej wersji pliku konfiguracyjnego i kontynuuje.

Przyszłe prace mogą sprawić, że skrypt sed będzie bardziej wytrzymały, odfiltrować linie, które wyglądają dziwnie lub nie są ” t definicje itp., nie przerywaj komentarzy na końcu linii – ale na razie to wystarczy.

Odpowiedź

Możesz to zrobić:

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

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *