Brug config-fil til mit shell-script

Jeg har brug for at oprette en config-fil til mit eget script:
Her er et eksempel:

script:

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

Indhold i /home/myuser/test/config:

nam="Mark" sur="Brown" 

det fungerer!

Mit spørgsmål: er dette den rigtige måde at gør dette eller der “på andre måder?

Kommentarer

  • Variablerne skal være øverst. I ‘ m overrasket over, at det fungerer. Hvor som helst, hvorfor har du brug for en konfigurationsfil? Planlægger du at bruge disse variabler et andet sted?
  • Faheem, jeg har brug for variablerne, fordi mit script har mange muligheder: at bruge en konfigurationsfil vil forenkle scriptet. Tak
  • IMHO det er fint. Jeg ville gøre på denne måde.
  • abcde gør det også på denne måde og det er et ret stort program (for et shell-script). Du kan se det her .

Svar

source er ikke sikkert, da det udfører vilkårlig kode. Dette er muligvis ikke et problem for dig, men hvis filtilladelser er forkerte, kan det være muligt for en hacker med filsystemadgang at udføre kode som en privilegeret bruger ved at injicere kode i en konfigurationsfil, der er indlæst med et ellers sikret script, såsom et init script.

Indtil videre er den bedste løsning, jeg har været i stand til at identificere, den klodset genopfinde-hjulet-løsning:

myscript.conf

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

Brug af source, dette vil køre echo rm -rf / to gange samt ændre den kørende bruger “s $PROMPT_COMMAND. I stedet skal du gøre dette:

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 

Svar venligst, hvis du finder en sikkerhedsudnyttelse i min kode.

Kommentarer

  • FYI dette er en Bash version 4.0-løsning, som desværre er underlagt sindssyge licensproblemer pålagt af Apple og ikke er tilgængelig som standard på Mac
  • @Sukima Godt punkt. Jeg ‘ har tilføjet en version, der er kompatibel med Bash 3. Dens svaghed er, at den ikke håndterer * -indgange korrekt, men så hvad i Bash håndterer den karakter godt?
  • Det første script mislykkes, hvis adgangskoden indeholder en tilbageslag.
  • @Kusalananda Hvad hvis backslash er undsluppet? my\\password
  • Denne version tager flere sekunder at behandle min konfigurationsfil, jeg fandt løsningen fra gw0 at være meget hurtigere.

Svar

Analyser konfigurationsfilen, udfør den ikke.

Jeg skriver i øjeblikket en applikation på arbejdspladsen, der bruger en ekstrem simpel XML-konfiguration:

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

I shell-scriptet (“applikationen”) er det hvad jeg gør for at få ved brugernavnet (mere eller mere mindre, jeg har lagt det i en shell-funktion):

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

xml kommandoen er XMLStarlet , som er tilgængelig for de fleste enheder.

Jeg bruger XML, da andre dele af applikationen også beskæftiger sig med data kodet i XML-filer, så det var nemmest.

Hvis du foretrækker JSON, er der “s jq hvilket er en let at bruge shell JSON parser.

Min konfigurationsfil ser sådan ud i JS ON:

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

Og så ville jeg få brugernavnet i scriptet:

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

Kommentarer

  • At udføre scriptet har en række fordele og ulemper. De største ulemper er sikkerhed, hvis nogen kan ændre konfigurationsfilen, kan de udføre kode, og det er sværere at gøre det idiot-bevis. Fordelene er hastighed, på en simpel test er det mere end 10.000 gange hurtigere at hente en konfigurationsfil end at køre pq, og fleksibilitet, enhver, der kan lide ape-patch-python, vil sætte pris på dette.
  • @icarus Hvor stor konfigurationsfiler støder du normalt på, og hvor ofte har du brug for at analysere dem i en session? Bemærk også, at der kan være flere værdier ud af XML eller JSON på én gang.
  • Normalt kun få (1 til 3) værdier. Hvis du bruger eval til at indstille flere værdier, udfører du valgte dele af konfigurationsfilen :-).
  • @icarus Jeg tænkte arrays … Ingen grund til at eval noget. Performance-hit for at bruge et standardformat med en eksisterende parser (selvom det ‘ er en ekstern hjælpeprogram) er ubetydelig i forhold til robustheden, mængden af kode, brugervenligheden og vedligeholdelsesevne.
  • +1 for ” parse konfigurationsfilen, don ‘ t udfør den ”

Svar

Her er en ren og bærbar version, der er kompatibel med Bash 3 og op, både på Mac og Linux.

Det specificerer alle standardindstillinger i en separat fil for at undgå behovet for en enorm, rodet, duplikeret “standard” -konfigurationsfunktion i alle dine shell-scripts. Og det giver dig mulighed for at vælge mellem at læse med eller uden standardnedfald:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (dette er et bibliotek, så der er 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 andre scripts, hvor du vil læs konfigurationsværdier) :

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

Forklaring af testscriptet:

  • Bemærk, at alle anvendelser af config_get i test.sh er pakket med dobbelt anførselstegn. Ved at indpakke hvert config_get i dobbelt anførselstegn, sikrer vi, at tekst i variabelværdien aldrig fejlagtigt fortolkes som flag. Og det sikrer, at vi bevarer det hvide rum korrekt, f.eks. Flere mellemrum i træk i konfigurationsværdien.
  • Og hvad er det printf -linjen? Nå, det “noget, du skal være opmærksom på: echo er en dårlig kommando til udskrivning af tekst, som du ikke har kontrol over. Selv hvis du bruger dobbelt anførselstegn, fortolker det flag. Prøv at indstille myvar (i config.cfg) til -e, og du vil se en tom linje, fordi echo vil tro, at det er et flag. Men printf har ikke det problem. printf -- siger “udskriv dette, og fortolk ikke noget som flag”, og "%s\n" siger “formatere output som en streng med en efterfølgende ny linje, og endelig er den sidste parameter værdien for printf at formatere.
  • Hvis du ikke vil ekko værdier til skærmen, skal du blot tildele dem normalt, som myvar="$(config_get myvar)";. Hvis du vil udskrive dem på skærmen, foreslår jeg at bruge printf for at være helt sikker mod alle ekko-inkompatible strenge, der måtte være i brugerkonfigurationen. Men ekko er fint, hvis den bruger-leverede variabel ikke er det første tegn i strengen, du gentager, da det er den eneste situation, hvor “flag” kunne fortolkes, så noget som echo "foo: $(config_get myvar)"; er sikkert, da ” foo “begynder ikke med et bindestreg og fortæller derfor ekko, at resten af strengen heller ikke markerer for det. 🙂

Kommentarer

  • @ user2993656 Tak, fordi du opdagede, at min oprindelige kode stadig havde mit private konfigurationsfilnavn (miljø.cfg) i stedet for det rigtige. Hvad angår ” echo -n ” redigering, du gjorde, det afhænger af den anvendte shell. På Mac / Linux Bash, ” echo -n ” betyder ” ekko uden efterfølgende ny linje “, hvilket jeg gjorde for at undgå at følge nye linjer. Men det ser ud til at arbejde nøjagtigt det samme uden det, så tak for redigeringerne!
  • Faktisk gik jeg bare igennem og omskrev det til at bruge printf i stedet for ekko, hvilket sikrer, at vi ‘ slipper risikoen for, at ekko fejler ” flag ” i konfigurationsværdierne.
  • Jeg kan virkelig godt lide denne version. Jeg faldt config.cfg.defaults i stedet for at definere dem på tidspunktet for opkald til $(config_get var_name "default_value"). tritarget.org/static/…
  • Ligeledes – dette er fantastisk.

Svar

Den mest almindelige, effektive og korrekte måde er at bruge source eller . som en stenografisk form. For eksempel:

source /home/myuser/test/config 

eller

. /home/myuser/test/config 

Noget at overveje er dog sikkerhedsproblemer, som brug af en ekstra eksternt konfigurationsfil kan rejse, forudsat at yderligere kode kan indsættes. For mere information, herunder hvordan man finder og løser dette problem, vil jeg anbefale at se på afsnittet “Sikker det” i http://wiki.bash-hackers.org/howto/conffile#secure_it

Kommentarer

  • Jeg havde store forhåbninger til den artikel (kom også i mine søgeresultater), men forfatteren ‘ s forslag om at forsøge at bruge regex til at filtrere ondsindet kode er en øvelse i nytteløshed.
  • Proceduren med punkt, kræver en absolut sti?Med den relative fungerer det ikke ‘ t

Svar

Jeg bruger dette i mine 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 } 

Skal understøtte alle tegnkombinationer, undtagen nøgler kan ikke have = i dem, da det er separatoren. Alt andet fungerer.

% 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 

Dette er også helt sikkert, da det ikke bruger source eller eval.

Kommentarer

  • Er det ikke ‘ t dette antages for først at oprette konfigurationsfilen, hvis den ikke findes ‘ t? Den ‘ t, men touch -a "${path}" vil selvfølgelig sikre, at den eksisterer, også uden at frivoløst opdatere sin mtime.

Svar

Dette er kortfattet og sikker:

# 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 sikrer, at du kun får variablerne fra common.vars

Opdatering: En illustration af sikkerheden er, at

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

ikke producerer nogen berørte filer. Testet på mac med bash, dvs. ved hjælp af bsd env.

Kommentarer

  • Se bare hvordan evil1 og evil2 filer oprettes, hvis du sætter dette til komm on.vars “ `touch touch1 foo = omg boo = $ (touch evil2)` `
  • @pihentagy For mig producerer følgende ingen berørte filer env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Kører på mac.
  • faktisk, men har ikke adgang til foo. Jeg ‘ har prøvet env -i ... myscript.sh og inde i dette script er foo ikke defineret. Men hvis du fjerner ” affald “, fungerer det. Så tak for at forklare. : +1:

Svar

De fleste brugere, selvom ikke mange containere, har allerede git binær. Brug derfor ikke git config til applikationskonfigurationsstyring ved hjælp af en dedikeret ikke-modstridende konfigurationsfil som i eksemplerne nedenfor?

# 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 

For yderligere kommandoer, se dens mand side. Det er klogt først at sikre, at konfigurationsfilen findes:

touch -a ~/.myapp 

Svar

Denne virker sikker og kort. Du er velkommen til at knække dette hensynsløst. Jeg vil gerne vide om en bedre måde.

TL; DR;

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

Jeg bruger bash 4.3.48.

Det er også i overensstemmelse med bash --posix. Se bunden for test.

Men sh understøtter det ikke på grund af declare.


Grundlæggende test for dem, der ønsker bevis

Opret 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"  

Indlæs konfigurationen med uddraget

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

Output (se desinficeringsmiddel 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 

Lad os kontrollere værdier 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" 

Kontroller bivirkninger (ingen bivirkninger):

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

Tillæg. 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 

Svar

For mit scenario, source eller . w lige så fint, men jeg ville støtte lokale miljøvariabler (dvs. FOO=bar myscript.sh) med forrang over konfigurerede variabler. Jeg ønskede også, at konfigurationsfilen skulle redigeres af brugeren og være behagelig for nogen, der plejede at hente konfigurationsfiler, og holde den så lille / enkel som muligt for ikke at distrahere fra hovedfokuset i mit meget lille script.

Dette er hvad jeg 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 det væsentlige – det kontrollerer for variable definitioner (uden at være meget fleksibel med hvidt mellemrum) og omskriver disse linjer, så værdi konverteres til en standard for den variabel, og variablen ændres ikke, hvis den findes, ligesom XDG_CONFIG_HOME variablen ovenfor. Den kilder denne ændrede version af konfigurationsfilen og fortsætter.

Fremtidigt arbejde kan gøre sed scriptet mere robust, filtrere linjer, der ser underlige ud eller ikke er ” t definitioner osv., ikke bryde i slutningen af linjekommentarer – men det er godt nok for mig indtil videre.

Svar

Du kan gøre det:

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

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *