Använd konfigurationsfil för mitt skalskript

Jag måste skapa en konfigurationsfil för mitt eget skript:
Här är ett exempel:

skript:

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

Innehåll i /home/myuser/test/config:

nam="Mark" sur="Brown" 

som fungerar!

Min fråga: är detta rätt sätt att gör detta eller där ”på andra sätt?

Kommentarer

  • Variablerna ska vara högst upp. Jag ’ m förvånad över att det fungerar. Hur som helst, varför behöver du en konfigurationsfil? Planerar du att använda dessa variabler någon annanstans?
  • Faheem, jag behöver variablerna eftersom mitt skript har många alternativ: att använda en config-filen kommer att förenkla skriptet. Tack
  • IMHO det är bra. Jag skulle göra så här.
  • abcde gör det också på detta sätt och det är ett ganska stort program (för ett skalskript). Du kan titta på det här .

Svar

source är inte säkert eftersom det kommer att köra godtycklig kod. Det här kanske inte är ett problem för dig, men om filbehörigheterna är felaktiga kan det vara möjligt för en angripare med filsystemåtkomst att köra kod som en privilegierad användare genom att injicera kod i en konfigurationsfil som laddas med ett annars säkert skript, t.ex. init-skript.

Hittills är den bästa lösningen jag har kunnat identifiera den klumpiga återuppfinning-ratten-lösningen:

myscript.conf

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

Med source, detta skulle köra echo rm -rf / två gånger, samt ändra den löpande användaren ”s $PROMPT_COMMAND. Istället gör du detta:

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 (Mac / Bash 3-kompatibel)

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

Svara om du hittar en säkerhetsutnyttjande i min kod.

Kommentarer

  • FYI detta är en Bash version 4.0-lösning som tyvärr är föremål för galen licensieringsfrågor införd av Apple och inte är tillgänglig som standard på Mac
  • @Sukima Bra poäng. Jag ’ har lagt till en version som är kompatibel med Bash 3. Dess svaghet är att den inte hanterar * ingångar ordentligt, men sedan vad i Bash hanterar den karaktären bra?
  • Det första skriptet misslyckas om lösenordet innehåller en snedstreck.
  • @Kusalananda Vad händer om snedstrecket undviks? my\\password
  • Den här versionen tar flera sekunder att bearbeta min konfigurationsfil, jag tyckte att lösningen från gw0 var mycket snabbare.

Svar

Analysera konfigurationsfilen, kör inte den.

Jag skriver för närvarande en applikation på jobbet som använder en extremt enkel XML-konfiguration:

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

I skalskriptet (”applikationen”) är det här jag gör för att få användarnamnet (mer eller mer mindre, jag har lagt den i en skalfunktion):

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

xml -kommandot är XMLStarlet , som är tillgängligt för de flesta enheter.

Jag använder XML eftersom andra delar av applikationen också handlar om data som är kodade i XML-filer, så det var lättast.

Om du föredrar JSON finns ”s jq vilket är ett lättanvänd skal-JSON-parser.

Min konfigurationsfil ser ungefär så ut i JS PÅ:

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

Och då skulle jag få användarnamnet i skriptet:

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

Kommentarer

  • Att utföra skriptet har ett antal fördelar och nackdelar. De största nackdelarna är säkerhet, om någon kan ändra konfigurationsfilen kan de köra kod och det är svårare att göra det idiot bevis. Fördelarna är hastighet, på ett enkelt test är det mer än 10 000 gånger snabbare att hämta en konfigurationsfil än att köra pq och flexibilitet, alla som gillar apa-lapp-python kommer att uppskatta detta. konfigurationsfiler stöter du vanligtvis på och hur ofta behöver du analysera dem i en session? Lägg också märke till att flera värden kan tas från XML eller JSON på en gång.
  • Vanligtvis bara ett fåtal (1 till 3) värden. Om du använder eval för att ställa in flera värden så kör du valda delar av konfigurationsfilen :-).
  • @icarus Jag tänkte arrays … Inget behov av att eval någonting. Prestandahittet med att använda ett standardformat med en befintlig parser (även om det ’ är ett externt verktyg) är försumbar jämfört med robustheten, mängden kod, användarvänligheten och underhållsförmåga.
  • +1 för ” analysera konfigurationsfilen, gör ’ t kör den ”

Svar

Här är en ren och bärbar version som är kompatibel med Bash 3 och upp, både på Mac och Linux.

Den specificerar alla standardvärden i en separat fil för att undvika behovet av en enorm, rörig, duplicerad ”standard” -konfigurationsfunktion i alla dina skalskript. Och det låter dig välja mellan att läsa med eller utan standardfall:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (detta är ett bibliotek, så det finns ingen shebang-linje):

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 (eller några skript där du vill läs konfigurationsvärden) :

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

Förklaring av testskriptet:

  • Observera att alla användningar av config_get i test.sh är inslagna i dubbla citat. Genom att sätta in alla config_get i dubbla citat ser vi till att texten i variabelvärdet aldrig tolkas felaktigt som flaggor. Och det säkerställer att vi bevarar det vita utrymmet ordentligt, till exempel flera mellanslag i rad i konfigurationsvärdet.
  • Och vad är det för printf? ”något du bör vara medveten om: echo är ett dåligt kommando för att skriva ut text som du inte har kontroll över. Även om du använder dubbla citat tolkar det flaggor. Försök att ställa in myvar (i config.cfg) till -e så visas en tom rad, eftersom echo tror att det är en flagga. Men printf har inte det problemet. printf -- säger ”skriv ut detta och tolk inte något som flaggor” och "%s\n" säger ”formatera utdata som en sträng med en efterföljande ny rad, och slutligen är den sista parametern värdet för printf att formatera.
  • Om du inte kommer att upprepa värden på skärmen, skulle du helt enkelt tilldela dem normalt, som myvar="$(config_get myvar)";. Om du ska skriva ut dem på skärmen föreslår jag att du använder printf för att vara helt säker mot alla ekokompatibla strängar som kan finnas i användarkonfigurationen. Men eko är bra om den användarinställda variabeln inte är det första tecknet i strängen du upprepar, eftersom det är den enda situationen där ”flaggor” kan tolkas, så något som echo "foo: $(config_get myvar)"; är säkert, eftersom ” foo ”börjar inte med en streck och säger därför till eko att resten av strängen inte heller flaggar för den. 🙂

Kommentarer

  • @ user2993656 Tack för att du upptäckte att min ursprungliga kod fortfarande hade mitt privata konfigurationsfilnamn (miljö.cfg) istället för rätt. När det gäller ” echo -n ” redigera du gjorde, det beror på det använda skalet. På Mac / Linux Bash, ” echo -n ” betyder ” eko utan efterföljande ny linje ”, vilket jag gjorde för att undvika att följa nya rader. Men det verkar fungera exakt samma utan det, så tack för redigeringarna!
  • Egentligen gick jag igenom och skrev om det för att använda printf istället för eko, vilket säkerställer att vi ’ kommer att bli av med risken för att ekotolkar ” flaggor ” i konfigurationsvärdena.
  • Jag gillar verkligen den här versionen. Jag tappade config.cfg.defaults istället för att definiera dem när jag ringde $(config_get var_name "default_value"). tritarget.org/static/…
  • Likaså – det här är fantastiskt.

Svar

Det vanligaste, effektivaste och korrekta sättet är att använda source eller . som en stenografisk form. Till exempel:

source /home/myuser/test/config 

eller

. /home/myuser/test/config 

Något att tänka på är dock säkerhetsproblem som kan användas med ytterligare en extern konfigurationsfil med utgångspunkt, med tanke på att ytterligare kod kan infogas. För mer information, inklusive hur du upptäcker och löser problemet, rekommenderar jag att du tittar på avsnittet ”Säkra det” i http://wiki.bash-hackers.org/howto/conffile#secure_it

Kommentarer

  • Jag hade stora förhoppningar på den artikeln (kom också upp i mina sökresultat), men författaren ’ förslag på att försöka använda regex för att filtrera bort skadlig kod är en övning i meningslöshet.
  • Proceduren med punkt, kräver en absolut sökväg?Med den relativa fungerar den inte ’ t

Svar

Jag använder detta i mina skript:

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 } 

Ska stödja alla teckenkombinationer, utom tangenterna kan inte ha = i dem, eftersom det är separatorn. Allt annat fungerar.

% 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 

Detta är också helt säkert eftersom det inte använder source eller eval.

Kommentarer

  • Är inte ’ t detta antas att först skapa konfigurationsfilen om den inte finns ’ t? Den finns inte ’ t men touch -a "${path}" kommer naturligtvis att säkerställa att den existerar, även utan att frivolikt uppdatera sin mtime.

Svar

Detta är kortfattad och säker:

# 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 säkerställer att du bara får variablerna från common.vars

Uppdatering: En säkerhetsillustration är att

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

Ger inga berörda filer. Testat på mac med bash, dvs med hjälp av bsd-env.

Kommentarer

  • Se bara hur evil1 och evil2-filer skapas om du lägger detta till komm on.vars touch evil1 foo = omg boo = $ (touch evil2) ”
  • @pihentagy För mig producerar följande inga berörda filer env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Körs på mac.
  • verkligen, men kan inte komma åt foo. Jag ’ har försökt env -i ... myscript.sh och inuti det skriptet definieras inte foo. Om du tar bort ” sopor ” fungerar det dock. Så tack för att du förklarade. : +1:

Svar

De flesta användare, även om inte många containrar, har redan git binärt. Varför inte därför använda git config för applikationskonfigurationshantering med en dedikerad icke-motstridig konfigurationsfil som i exemplen nedan?

# 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 

För ytterligare kommandon, se dess man sida. Det är klokt att först se till att konfigurationsfilen finns:

touch -a ~/.myapp 

Svar

Den här verkar säker och kort. Känn dig fri att knäcka detta hänsynslöst. Jag skulle vilja veta om ett bättre sätt.

TL; DR;

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

Jag använder bash 4.3.48.

Det är också kompatibelt med bash --posix. Se botten för test.

Men sh stöder det inte på grund av declare.


Grundläggande test för dem som vill ha bevis

Skapa fil 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"  

Ladda konfigurationen med kodavsnittet

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

Output (se sanitizer i aktion)

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 

Låt oss kontrollera värdena nu

 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" 

Kontrollera biverkningar (inga biverkningar):

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

Bilaga. Testa 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 

Svar

För mitt scenario, source eller . w lika bra, men jag ville stödja lokala miljövariabler (dvs. FOO=bar myscript.sh) med företräde framför konfigurerade variabler. Jag ville också att konfigurationsfilen skulle kunna redigeras av användaren och vara bekväm för någon som använde sig av konfigurationsfiler och att den skulle vara så liten / enkel som möjligt, för att inte distrahera från huvudinriktningen i mitt mycket lilla skript.

Det här är vad jag kom på:

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) 

I grund och botten – det söker efter variabla definitioner (utan att vara mycket flexibel när det gäller blanksteg) och skriver om dessa rader så att värdet konverteras till en standard för den variabeln, och variabeln är omodifierad om den hittas, som XDG_CONFIG_HOME variabeln ovan. Den källor till den här ändrade versionen av konfigurationsfilen och fortsätter.

Framtida arbete kan göra sed -skriptet mer robust, filtrera ut linjer som ser konstiga ut eller inte t definitioner, etc, inte bryta i slutet av radkommentarer – men det är tillräckligt bra för mig för tillfället.

Svar

Du kan göra det:

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

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *