Utilizați fișierul de configurare pentru scriptul meu shell

Am nevoie să creez un fișier de configurare pentru propriul meu script:
Iată un exemplu:

script:

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

Conținutul /home/myuser/test/config:

nam="Mark" sur="Brown" 

care funcționează!

Întrebarea mea: acesta este modul corect de a faceți acest lucru sau există „alte modalități?

Comentarii

  • Variabilele ar trebui să fie în partea de sus. I ‘ M-am surprins că funcționează. Oricum, de ce aveți nevoie de un fișier de configurare? Plănuiți să utilizați aceste variabile în altă parte?
  • Faheem, am nevoie de variabile deoarece scriptul meu are multe opțiuni: folosind un fișierul de configurare va simplifica scriptul. Vă mulțumim
  • IMHO este bine. Aș face acest lucru.
  • abcde o face și în acest fel și acesta este un program destul de mare (pentru un script shell). Puteți să-l priviți aici .

Răspuns

source nu este sigur, deoarece va executa cod arbitrar. Este posibil să nu vă preocupe, dar dacă permisiunile de fișiere sunt incorecte, poate fi posibil ca un atacator cu acces la sistemul de fișiere să execute codul ca utilizator privilegiat prin injectarea de cod într-un fișier de configurare încărcat de un script securizat altfel, cum ar fi un script ini.

Până acum, cea mai bună soluție pe care am reușit să o identific este soluția neîndemânatică de reinventare a roții:

myscript.conf

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

Utilizarea source, aceasta ar rula echo rm -rf / de două ori, precum și schimbarea utilizatorului care rulează „s $PROMPT_COMMAND. În schimb, faceți acest lucru:

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 (compatibil 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 

Vă rugăm să răspundeți dacă găsiți un exploit de securitate în codul meu.

Comentarii

  • FYI, aceasta este o soluție Bash versiunea 4.0, care din păcate este supusă problemelor de licențiere nebune impuse de Apple și nu este disponibilă în mod implicit pe Mac-uri
  • @Sukima Bun punct. Am ‘ am adăugat o versiune care este compatibilă cu Bash 3. Punctul său slab este că nu va gestiona * în intrări corect, dar apoi ce în Bash tratează bine acel caracter?
  • Primul script nu reușește dacă parola conține o bară inversă.
  • @Kusalananda Ce se întâmplă dacă bară inversă este scăpată? my\\password
  • Această versiune durează câteva secunde pentru a-mi procesa fișierul de configurare, am găsit că soluția din gw0 este mult mai rapidă.

Răspuns

Analizați fișierul de configurare, nu-l executați.

În prezent scriu o aplicație la locul de muncă care folosește o configurație XML extrem de simplă:

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

În scriptul shell („aplicația”), asta fac pentru a obține numele de utilizator (mai mult sau mai puțin, l-am „pus într-o funcție shell):

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

Comanda xml este XMLStarlet , care este disponibil pentru majoritatea unităților.

Folosesc XML deoarece alte părți ale aplicației se ocupă și de date codificate în fișiere XML, deci a fost cel mai ușor.

Dacă preferați JSON, există „s jq care este un parser shell JSON ușor de utilizat.

Fișierul meu de configurare ar arăta cam așa în JS ON:

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

Și apoi aș primi numele de utilizator în script:

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

Comentarii

  • Executarea scriptului are o serie de avantaje și dezavantaje. Principalele dezavantaje sunt securitatea, dacă cineva poate modifica fișierul de configurare, atunci poate executa cod și este mai greu să-l faceți idiot. Avantajele sunt viteza, la un test simplu, este mai mult de 10.000 de ori mai rapid să sursați un fișier de configurare decât să rulați pq și flexibilitate, oricui îi place maimuțele de patch-uri python va aprecia acest lucru.
  • @icarus Cât de mare fișierele de configurare pe care le întâlniți de obicei și cât de des trebuie să le analizați într-o singură sesiune? Observați și faptul că mai multe valori pot fi obținute dintr-un XML sau JSON dintr-o dată.
  • De obicei, doar câteva (1 până la 3) valori. Dacă utilizați eval pentru a seta mai multe valori, atunci executați părți selectate ale fișierului de configurare :-).
  • @icarus Mă gândeam matrici … Nu este nevoie să eval nimic. Performanța utilizării unui format standard cu un analizor existent (chiar dacă ‘ este un utilitar extern) este neglijabilă în comparație cu robustețea, cantitatea de cod, ușurința de utilizare , și mentenabilitate.
  • +1 pentru ” analizați fișierul de configurare, nu ‘ nu îl executați ”

Răspuns

Iată o versiune curată și portabilă care este compatibilă cu Bash 3 și sus, atât pe Mac, cât și pe Linux.

Specifică toate valorile implicite într-un fișier separat, pentru a evita necesitatea unei funcții de configurare „implicite” uriașe, aglomerate, duplicate în toate scripturile dvs. shell. Și vă permite să alegeți între citirea cu sau fără soluții de rezervă implicite:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (aceasta este o bibliotecă, deci nu există nici o linie de 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 (sau orice scripturi unde doriți să citește valorile de configurare) :

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

Explicația scriptului de testare:

  • Rețineți că toate utilizările config_get din test.sh sunt înfășurate în ghilimele duble. Înfășurând fiecare config_get în ghilimele duble, ne asigurăm că textul din valoarea variabilă nu va fi niciodată interpretat greșit ca steaguri. Și se asigură că păstrăm spațiul alb în mod corespunzător, cum ar fi mai multe spații la rând în valoarea de configurare.
  • Și ce este linia printf? Ei bine, este „Este ceva de care ar trebui să știți: echo este o comandă greșită pentru tipărirea textului asupra căruia nu aveți control. Chiar dacă utilizați ghilimele duble, acesta va interpreta steagurile. Încercați să setați myvar (în config.cfg) la -e și veți vedea o linie goală, deoarece echo va crede că este „un steag. Dar printf nu are această problemă. printf -- spune „tipăriți acest lucru și nu interpretați nimic ca steaguri”, iar "%s\n" spune „formatați ieșirea ca un șir cu o linie nouă finală și, în sfârșit, parametrul final este valoarea pe care printf trebuie să o formateze.
  • Dacă nu veți răsuna valorile pe ecran, atunci le-ați atribui pur și simplu în mod normal, cum ar fi myvar="$(config_get myvar)";. Dacă doriți să le imprimați pe ecran, vă sugerăm să folosiți printf pentru a fi în siguranță împotriva oricăror șiruri incompatibile cu ecoul care pot fi în configurația utilizatorului. Dar ecoul este în regulă dacă variabila furnizată de utilizator nu este „t primul caracter al șirului de care sunteți ecou, deoarece aceasta este singura situație în care „steagurile” ar putea fi interpretate, așa că ceva de genul echo "foo: $(config_get myvar)"; este sigur, deoarece foo „nu începe cu o liniuță și, prin urmare, spune ecou că restul șirului nu este nici un semn pentru acesta. 🙂

Comentarii

  • @ user2993656 Vă mulțumim că ați observat că codul meu original avea în continuare numele meu de fișier de configurare privat (environment.cfg) în locul celui corect. În ceea ce privește ” editarea echo -n ” pe care ați făcut-o, aceasta depinde de shell-ul folosit. Pe Mac / Linux Bash, ” echo -n ” înseamnă ” ecou fără a urma linia nouă „, pe care l-am făcut pentru a evita urmărirea liniilor noi. Dar se pare că funcționează exact la fel fără el, așa că mulțumesc pentru modificări!
  • De fapt, tocmai am trecut și l-am rescris pentru a folosi printf în loc de ecou, ceea ce ne asigură că ‘ voi scăpa de riscul de a interpreta greșit ecoul ” steaguri ” în valorile de configurare.
  • Îmi place foarte mult această versiune. Am renunțat la config.cfg.defaults în loc să le definesc în momentul apelării $(config_get var_name "default_value"). tritarget.org/static/…
  • La fel – este minunat.

Răspuns

Cel mai comun, eficient și corect mod este de a utiliza source, sau . ca formă stenogramă. De exemplu:

source /home/myuser/test/config 

sau

. /home/myuser/test/config 

Totuși, ceva de luat în considerare este probleme de securitate pe care le poate ridica utilizarea unui fișier de configurare suplimentar extern, având în vedere că se poate introduce un cod suplimentar. Pentru mai multe informații, inclusiv despre cum să detectați și să rezolvați această problemă, vă recomand să aruncați o privire la secțiunea „Securizați-l” din http://wiki.bash-hackers.org/howto/conffile#secure_it

Comentarii

  • Am avut mari speranțe pentru acel articol (am apărut și în rezultatele mele de căutare), dar autorul ‘ de a încerca să folosiți regex pentru a filtra codul rău intenționat este un exercițiu inutil.
  • Procedura cu punct, necesită o cale absolută?Cu cea relativă nu funcționează ‘ t funcționează

Răspunde

Folosesc acest lucru în scripturile mele:

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 } 

Ar trebui să accepte fiecare combinație de caractere, cu excepția tastelor care nu pot = în ele, deoarece acesta este separatorul. Orice altceva funcționează.

% 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 

De asemenea, acest lucru este complet sigur, deoarece nu folosește source sau eval.

Comentarii

  • Nu este ‘ pentru a crea mai întâi fișierul de configurare dacă nu ‘ nu există? Nu ‘ t, dar touch -a "${path}" se va asigura, desigur, că există, de asemenea, fără a-și actualiza în mod frivol timpul.

Răspuns

Acesta este succint și sigur:

# 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 vă asigură că obțineți numai variabilele de la common.vars

Actualizare: o ilustrare a securității este că

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

Nu va produce fișiere atinse. Testat pe Mac cu bash, adică folosind bsd env.

Comentarii

  • Vedeți doar cum sunt create fișierele evil1 și evil2 dacă puneți acest lucru în comunicare on.vars „” touch evil1 foo = omg boo = $ (touch evil2) „” „
  • @pihentagy Pentru mine următoarele nu produc fișiere atinse env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Rulează pe Mac.
  • într-adevăr, dar nu poate accesa foo. Am ‘ am încercat env -i ... myscript.sh și în interiorul acelui script foo nu este definit. Cu toate acestea, dacă eliminați ” gunoi „, acesta va funcționa. Deci, mulțumesc pentru explicații. : +1:

Răspuns

Majoritatea utilizatorilor, deși nu sunt multe containere, au deja git binar. De ce nu folosiți, prin urmare, git config pentru gestionarea configurației aplicației utilizând un fișier de configurare dedicat neconflictiv ca în exemplele de mai jos?

# 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 

Pentru comenzi suplimentare, consultați manualul său pagină. Este înțelept să vă asigurați mai întâi că fișierul de configurare există:

touch -a ~/.myapp 

Răspuns

Acesta pare sigur și scurt. Simțiți-vă liber să spargeți acest lucru nemilos. „Aș vrea să știu despre un mod mai bun.

TL; DR;

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

Eu folosesc bash 4.3.48.

Este, de asemenea, conform cu bash --posix. Consultați partea de jos pentru testare.

Dar sh nu o acceptă din cauza declare.


Test de bază pentru cei care doresc dovezi

Creați fișier 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"  

Încărcați configurarea cu fragmentul

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

Ieșire (vezi dezinfectant în acțiune)

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 

Să verificăm valorile acum

 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" 

Verificați efectele secundare (fără efecte secundare):

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

Anexă. 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 

Răspuns

Pentru scenariul meu, source sau . w la fel de bine, dar am vrut să susțin variabilele de mediu locale (adică FOO=bar myscript.sh) având prioritate față de variabilele configurate. De asemenea, am dorit ca fișierul de configurare să poată fi modificat de utilizator și să fie confortabil pentru cineva obișnuit să obțină fișiere de configurare și să-l păstreze cât mai mic / simplu posibil, pentru a nu distrage atenția de la elementul principal al micului meu script. > Iată ce am venit:

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) 

În esență – verifică definițiile variabilelor (fără a fi foarte flexibile în ceea ce privește spațiul alb) și rescrie acele linii astfel încât valoarea este convertită la o valoare implicită pentru acea variabilă, iar variabila este nemodificată dacă este găsită, cum ar fi variabila XDG_CONFIG_HOME de mai sus. Descarcă această versiune modificată a fișierului de configurare și continuă.

Lucrările viitoare ar putea face scriptul sed mai robust, filtrează liniile care par ciudate sau care nu sunt ” Definițiile, etc, nu se întrerup la comentariile de la sfârșitul rândului – dar acest lucru este suficient de bun pentru mine pentru moment.

Răspuns

Puteți face acest lucru:

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

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *