Usa il file di configurazione per il mio script di shell

Ho bisogno di creare un file di configurazione per il mio script:
Ecco un esempio:

script:

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

Contenuto di /home/myuser/test/config:

nam="Mark" sur="Brown" 

che funziona!

La mia domanda: è questo il modo corretto di fare questo o ci sono altri modi?

Commenti

  • Le variabili dovrebbero essere in cima. I ‘ sono sorpreso che funzioni. Comunque, perché hai bisogno di un file di configurazione? Hai intenzione di usare queste variabili da qualche altra parte?
  • Faheem, ho bisogno delle variabili perché il mio script ha molte opzioni: usando un config semplificherà lo script. Grazie
  • IMHO va bene. Lo farei in questo modo.
  • abcde lo fa anche in questo modo e è un programma abbastanza grande (per uno script di shell). Puoi dare unocchiata qui .

Answer

source non è sicuro in quanto eseguirà codice arbitrario. Questo potrebbe non essere un problema per te, ma se i permessi dei file non sono corretti, potrebbe essere possibile per un utente malintenzionato con accesso al file system eseguire codice come utente privilegiato inserendo codice in un file di configurazione caricato da uno script altrimenti protetto come un init script.

Finora, la migliore soluzione che sono stato in grado di identificare è la goffa soluzione reinventare la ruota:

myscript.conf

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

Utilizzando source, questo verrebbe eseguito echo rm -rf / due volte, oltre a modificare lutente in esecuzione “s $PROMPT_COMMAND. Invece, fai questo:

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 (compatibile con 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 

Per favore rispondi se trovi un exploit di sicurezza nel mio codice.

Commenti

  • Cordiali saluti, questa è una soluzione Bash versione 4.0 che purtroppo è soggetta a problemi di licenza insani imposti da Apple e non è disponibile per impostazione predefinita su Mac
  • @Sukima Buon punto. Ho ‘ ho aggiunto una versione compatibile con Bash 3. Il suo punto debole è che non gestisce correttamente * negli input, ma poi cosa in Bash gestisce bene quel carattere?
  • Il primo script fallisce se la password contiene una barra rovesciata.
  • @Kusalananda E se la barra rovesciata è sfuggita? my\\password
  • Questa versione impiega diversi secondi per elaborare il mio file di configurazione, ho scoperto che la soluzione di gw0 è molto più veloce.

Risposta

Analizza il file di configurazione, non eseguirlo.

Attualmente sto scrivendo unapplicazione al lavoro che utilizza una configurazione XML estremamente semplice:

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

Nello script della shell (l “applicazione”), questo è ciò che faccio per ottenere il nome utente (più o less, lho “inserito in una funzione shell):

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

Il comando xml è XMLStarlet , disponibile per la maggior parte degli Unix.

Utilizzo XML poiché anche altre parti dellapplicazione si occupano di dati codificati in file XML, quindi è stato più semplice.

Se preferisci JSON, cè “s jq che è un parser JSON della shell facile da usare.

Il mio file di configurazione sarebbe simile a questo in JS ON:

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

E poi “vorrei ottenere il nome utente nello script:

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

Commenti

  • Lesecuzione dello script presenta una serie di vantaggi e svantaggi. I principali svantaggi sono la sicurezza, se qualcuno può alterare il file di configurazione, può eseguire il codice ed è più difficile renderlo a prova di idiota. I vantaggi sono la velocità, in un semplice test è più di 10.000 volte più veloce generare un file di configurazione che eseguire pq, e la flessibilità, chiunque ami le patch di scimmie python lo apprezzerà.
  • @icarus Quanto è grande file di configurazione che incontri di solito e quanto spesso hai bisogno di analizzarli in una sessione? Si noti inoltre che diversi valori possono essere ricavati da XML o JSON in una volta sola.
  • Di solito solo pochi (da 1 a 3) valori. Se stai usando eval per impostare più valori, stai eseguendo parti selezionate del file di configurazione :-).
  • @icarus stavo pensando agli array … Non è necessario eval nulla. Limpatto sulle prestazioni dellutilizzo di un formato standard con un parser esistente (anche se ‘ è unutilità esterna) è trascurabile rispetto alla robustezza, alla quantità di codice, alla facilità duso e manutenibilità.
  • +1 per ” analizzare il file di configurazione, don ‘ t eseguirlo ”

Risposta

Ecco una versione pulita e portatile compatibile con Bash 3 e up, sia su Mac che su Linux.

Specifica tutti i valori di default in un file separato, per evitare la necessità di una funzione di configurazione “default” enorme, disordinata e duplicata in tutti gli script della shell. Inoltre, ti consente di scegliere tra la lettura con o senza i fallback predefiniti:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (questa è una libreria, quindi non cè nessuna riga):

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 (o qualsiasi script in cui desideri leggi i valori di configurazione) :

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

Spiegazione dello script di test:

  • Nota che tutti gli usi di config_get in test.sh sono racchiusi tra virgolette doppie. Racchiudendo ogni config_get tra virgolette doppie, ci assicuriamo che il testo nel valore della variabile mai venga interpretato erroneamente come flag. E assicura che conserviamo correttamente gli spazi bianchi, ad esempio più spazi di fila nel valore di configurazione.
  • E cosè quella riga printf? “è qualcosa di cui dovresti essere a conoscenza: echo è un cattivo comando per stampare testo su cui non hai alcun controllo. Anche se usi le virgolette doppie, interpreterà i flag. Prova a impostare myvar (in config.cfg) su -e e vedrai una riga vuota, perché echo penserà che sia “un flag. Ma printf non ha questo problema. printf -- dice “stampa questo e non” interpretare nulla come flag “e "%s\n" dice” formatta loutput come una stringa con una nuova riga finale, e infine il parametro finale è il valore che printf deve formattare.
  • Se non stai facendo eco a valori sullo schermo, allora devi semplicemente assegnarli normalmente, come myvar="$(config_get myvar)";. Se vuoi stamparli sullo schermo, ti suggerisco di usare printf per essere totalmente sicuro contro qualsiasi stringa incompatibile con leco che potrebbe essere nella configurazione dellutente. Ma echo va bene se la variabile fornita dallutente non è “t il primo carattere della stringa che stai ripetendo, poiché “è lunica situazione in cui” flags “potrebbe essere interpretato, quindi qualcosa come echo "foo: $(config_get myvar)"; è sicuro, poiché” foo “non” inizia con un trattino e quindi dice a echo che anche il resto della stringa non è “t flag”. 🙂

Commenti

  • @ user2993656 Grazie per aver notato che il mio codice originale conteneva ancora il mio nome file di configurazione privato (environment.cfg) invece di quello corretto. Come per il ” echo -n ” modifica eseguita, dipende dalla shell utilizzata. Su Mac / Linux Bash, ” echo -n ” significa ” echo senza fine riga “, cosa che ho fatto per evitare di finire le nuove righe. Ma sembra funzionare esattamente allo stesso modo senza di essa, quindi grazie per le modifiche!
  • In realtà, lho appena riscritto per utilizzare printf anziché echo, il che garantisce che ‘ elimina il rischio di uninterpretazione errata delleco di ” flag ” nei valori di configurazione.
  • Mi piace molto questa versione. Ho eliminato config.cfg.defaults invece di definirli al momento della chiamata a $(config_get var_name "default_value"). tritarget.org/static/…
  • Allo stesso modo, è fantastico.

Risposta

Il modo più comune, efficiente e corretto è utilizzare source o . come forma abbreviata. Ad esempio:

source /home/myuser/test/config 

o

. /home/myuser/test/config 

Qualcosa da considerare, tuttavia, è il problemi di sicurezza che possono sollevare utilizzando un file di configurazione esterno aggiuntivo, dato che è possibile inserire codice aggiuntivo. Per ulteriori informazioni, incluso su come rilevare e risolvere questo problema, ti consiglio di dare unocchiata alla sezione “Proteggilo” di http://wiki.bash-hackers.org/howto/conffile#secure_it

Commenti

  • Avevo grandi speranze per quellarticolo (è apparso anche nei miei risultati di ricerca), ma lautore ‘ di tentare di utilizzare regex per filtrare il codice dannoso è un esercizio inutile.
  • La procedura con punto, richiede un percorso assoluto?Con quello relativo non ‘ non funziona

Risposta

Lo uso nei miei script:

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 } 

Dovrebbe supportare ogni combinazione di caratteri, eccetto le chiavi che non possono = in loro, poiché quello è il separatore. Qualsiasi altra cosa funziona.

% 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 

Inoltre, questo è completamente sicuro poiché “t non utilizza source o eval.

Commenti

  • Non è ‘ questo supposto per creare prima il file di configurazione se ‘ non esiste? Non ‘ t, ma touch -a "${path}" ovviamente garantirà che esista, anche senza aggiornare in modo frivolo il suo mtime.

Answer

Questo è succinto e sicuro:

# 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 ti assicura di ottenere solo le variabili da common.vars

Aggiornamento: un esempio della sicurezza è che

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

non produrrà alcun file modificato. Testato su mac con bash, cioè usando bsd env.

Commenti

  • Guarda come vengono creati i file evil1 e evil2 se lo metti in comm on.vars “ touch evil1 foo = omg boo = $ (touch evil2) “
  • @pihentagy Per me quanto segue non produce file modificati env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Funziona su mac.
  • in effetti, ma non è possibile accedere a foo. Ho ‘ ho provato env -i ... myscript.sh e allinterno di quello script foo non è definito. Tuttavia, se rimuovi ” garbage “, funzionerà. Quindi grazie per aver spiegato. : +1:

Risposta

La maggior parte degli utenti, sebbene non molti contenitori, hanno già git binario. Perché non utilizzare quindi git config per la gestione della configurazione dellapplicazione utilizzando un file di configurazione dedicato non in conflitto come negli esempi seguenti?

# 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 

Per ulteriori comandi, vedere il suo man pagina. È consigliabile prima assicurarsi che il file di configurazione esista:

touch -a ~/.myapp 

Risposta

Questo sembra corto e sicuro. Sentiti libero di risolvere questo problema senza pietà. “Vorrei conoscere un modo migliore.

TL; DR;

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

Sto usando bash 4.3.48.

È anche conforme a bash --posix. Vedi in basso per il test.

Ma sh non lo supporta a causa di declare.


Test di base per chi vuole una prova

Crea file 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"  

Carica la configurazione con lo snippet

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

Risultato (vedi disinfettante in azione)

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 

Controlliamo ora i valori

 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" 

Controlla gli effetti collaterali (senza effetti collaterali):

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

Appendice. 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 

Risposta

Per il mio scenario, source o . w altrettanto bene, ma volevo supportare le variabili di ambiente locali (ad esempio, FOO=bar myscript.sh) avendo la precedenza sulle variabili configurate. Volevo anche che il file di configurazione fosse modificabile dallutente e comodo per qualcuno abituato ai file di configurazione di origine, e che lo mantenesse il più piccolo / semplice possibile, per non distrarre dalla spinta principale del mio piccolissimo script.

Questo è ciò che mi è venuto in mente:

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) 

Essenzialmente – controlla le definizioni delle variabili (senza essere molto flessibile sugli spazi bianchi) e riscrive quelle righe in modo che il il valore viene convertito in un valore predefinito per quella variabile e la variabile non viene modificata se trovata, come la variabile XDG_CONFIG_HOME sopra. Fornisce questa versione modificata del file di configurazione e continua.

Il lavoro futuro potrebbe rendere lo script sed più robusto, filtrare le righe che sembrano strane o non sono ” Le definizioni, ecc., non interrompono i commenti di fine riga, ma per ora è abbastanza buono per me.

Risposta

Puoi farlo:

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

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *