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
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
(wconfig.cfg
) na-e
, a zobaczysz pusty wiersz, ponieważecho
pomyśli, że to „flaga”. Aleprintf
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 jakecho "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
*
danych wejściowych, ale co w Bash dobrze obsługuje ten znak?my\\password