Použít konfigurační soubor pro můj shell skript

Potřebuji vytvořit konfigurační soubor pro svůj vlastní skript:
Zde je příklad:

script:

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

Obsah /home/myuser/test/config:

nam="Mark" sur="Brown" 

to funguje!

Moje otázka: je to správný způsob, jak udělejte to nebo tam „jinými způsoby?

Komentáře

  • Proměnné by měly být nahoře. I ‚ m překvapeno, že to funguje. Mimochodem, proč potřebujete konfigurační soubor? Plánujete použít tyto proměnné někde jinde?
  • Faheem, potřebuji proměnné, protože můj skript má mnoho možností: použití a konfigurační soubor rozdělí skript na skript. Díky
  • IMHO je v pořádku. Udělal bych to takhle.
  • abcde to také dělá takto a to je docela velký program (pro shell skript). Můžete se na něj podívat zde .

Odpověď

source není zabezpečená, protože provede libovolný kód. To vás nemusí znepokojovat, ale pokud jsou oprávnění k souborům nesprávná, může útočník s přístupem k souborovému systému spustit kód jako privilegovaný uživatel vložením kódu do konfiguračního souboru načteného jinak zabezpečeným skriptem, například Init script.

Doposud nejlepším řešením, které jsem dokázal identifikovat, je neohrabané řešení reinventing-the-wheel:

myscript.conf

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

Pomocí source, spustilo by se to echo rm -rf / dvakrát a zároveň by se změnil běžící uživatel $PROMPT_COMMAND. Místo toho proveďte toto:

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 (kompatibilní s 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 

Odpovězte, pokud v mém kódu najdete bezpečnostní zneužití.

Komentáře

  • FYI, jedná se o řešení verze Bash verze 4.0, které bohužel podléhá šíleným problémům s licencemi uloženými společností Apple a není ve výchozím nastavení k dispozici na počítačích Mac
  • @Sukima Dobrý bod. Přidal jsem ‚ verzi kompatibilní s Bash 3. Jeho slabinou je, že nebude * správně zpracovávat vstupy, ale pak co v Bash zvládá tento znak dobře?
  • První skript selže, pokud heslo obsahuje zpětné lomítko.
  • @Kusalananda Co když je zpětné lomítko uniknuto? my\\password
  • Zpracování mého konfiguračního souboru této verzi trvá několik sekund, našel jsem řešení od gw0 mnohem rychlejší.

Odpovědět

Analyzovat konfigurační soubor, neprovádět jej.

V současné době píšu v práci aplikaci, která používá extrémně jednoduchou konfiguraci XML:

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

Ve skriptu shellu (dále jen „aplikace“) se k uživatelskému jménu dostanu takto (více nebo méně, vložil jsem to do funkce shellu):

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

Příkaz xml je XMLStarlet , který je k dispozici pro většinu Unices.

Používám XML, protože ostatní části aplikace také pracují s daty zakódovanými v souborech XML, takže bylo to nejjednodušší.

Pokud dáváte přednost formátu JSON, existuje jq což je snadno použitelný syntaktický analyzátor JSON.

Můj konfigurační soubor by v JS vypadal nějak takto ZAPNUTO:

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

A pak ve skriptu získám uživatelské jméno:

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

Komentáře

  • Spuštění skriptu má řadu výhod a nevýhod. Hlavní nevýhody jsou zabezpečení, pokud někdo může změnit konfigurační soubor, může spustit kód a je těžší udělat z něj idiotský důkaz. Výhodou je rychlost, při jednoduchém testu je více než 10 000krát rychlejší získání konfiguračního souboru než spuštění pq a flexibilita, ocení to každý, kdo má rád opičí opravu pythonu.
  • @icarus Jak velký konfigurační soubory, se kterými se obvykle setkáváte, a jak často je musíte analyzovat v jedné relaci? Všimněte si také, že z XML nebo JSON může být najednou několik hodnot.
  • Obvykle jen několik (1 až 3) hodnot. Pokud používáte eval k nastavení více hodnot, spouštíte vybrané části konfiguračního souboru :-).
  • @icarus Přemýšlel jsem o polích … Není třeba nic eval. Výkonnostní hit použití standardního formátu s existujícím analyzátorem (i když je ‚ s externím nástrojem) je zanedbatelný ve srovnání s robustností, množstvím kódu a snadným použitím a udržovatelnost.
  • +1 pro “ analyzovat konfigurační soubor, ‚ jej nespustit “

Odpověď

Zde je čistá a přenosná verze kompatibilní s Bash 3 a nahoru, na počítačích Mac i Linux.

Specifikuje všechny výchozí hodnoty v samostatném souboru, aby se ve všech vašich skriptech prostředí nepotřebovala obrovská přeplněná duplicitní konfigurační funkce „výchozí“. Umožňuje vám vybrat si mezi čtením s výchozími záložními zdroji nebo bez nich:

config.cfg :

myvar=Hello World 

config.cfg.defaults :

myvar=Default Value othervar=Another Variable 

config.shlib (toto je knihovna, takže neexistuje žádný řádek 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 (nebo jakékoli skripty, kam chcete číst konfigurační hodnoty) :

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

Vysvětlení testovacího skriptu:

  • Všimněte si, že všechna použití config_get v test.sh jsou uvedena v uvozovkách. Zabalením každého config_get do uvozovek zajistíme, že text v hodnotě proměnné nebude nikdy chybně interpretován jako příznaky. A zajišťuje, že mezery v hodnotě konfigurace řádně zachováme, například více mezer za sebou.
  • A co je to za printf řádek? „něco, čeho byste si měli být vědomi: echo je špatný příkaz pro tisk textu, nad kterým nemáte žádnou kontrolu. I když použijete uvozovky, bude to interpretovat příznaky. Zkuste nastavit myvar (v config.cfg) na -e a uvidíte prázdný řádek, protože echo si bude myslet, že je to příznak. Ale printf tento problém nemá. printf -- říká „vytiskněte to a nic nevykládejte jako příznaky“ a "%s\n" říká „formátujte výstup jako řetězec s koncovým novým řádkem a konečným parametrem je hodnota, kterou má formát printf formátovat.
  • Pokud na obrazovce nebudete odrážet hodnoty, jednoduše je přiřadíte normálně, například myvar="$(config_get myvar)";. Pokud je chcete tisknout na obrazovku, doporučuji použít printf, abyste byli zcela v bezpečí proti jakýmkoli řetězcům nekompatibilním s ozvěnou, které mohou být v uživatelské konfiguraci. Ale ozvěna je v pořádku, pokud uživatelská proměnná není první znak řetězce, který ozýváte, protože to je jediná situace, kdy lze „příznaky“ interpretovat, takže něco jako echo "foo: $(config_get myvar)"; je bezpečné, protože “ foo „nezačíná pomlčkou, a proto říká echo, že ani zbytek řetězce jej nenaznačuje. 🙂

Komentáře

  • @ user2993656 Děkujeme, že jste si všimli, že v mém původním kódu byl stále můj soukromý konfigurační název souboru (environment.cfg) namísto správného. Pokud jde o “ echo -n “ úpravy, které jste provedli, záleží na použitém prostředí. V systému Mac / Linux Bash “ echo -n “ znamená “ ozvěnu bez koncové řádky „, což jsem udělal, abych se vyhnul koncům nových řádků. Ale zdá se, že to funguje úplně stejně, takže díky za úpravy!
  • Vlastně jsem to prošel a přepsal, abych místo echa použil printf, což zajišťuje, že jsme ‚ Zbavím se rizika nesprávné interpretace ozvěny “ příznaků “ v hodnotách konfigurace.
  • Opravdu se mi tato verze líbí. Namísto jejich definování v době volání $(config_get var_name "default_value") jsem config.cfg.defaults upustil. tritarget.org/static/…
  • Podobně – to je skvělé.

Odpověď

Nejběžnějším, nejúčinnějším a správným způsobem je použití source, nebo . jako zkratkový formulář. Například:

source /home/myuser/test/config 

nebo

. /home/myuser/test/config 

Je však třeba vzít v úvahu problémy se zabezpečením, které může způsobit použití dalšího externího konfiguračního souboru, vzhledem k tomu, že lze vložit další kód. Další informace, včetně informací o tom, jak tento problém zjistit a vyřešit, doporučuji nahlédnout do sekce „Zabezpečit“ v http://wiki.bash-hackers.org/howto/conffile#secure_it

Komentáře

  • Do tohoto článku jsem vkládal velké naděje (objevil se také ve výsledcích vyhledávání), ale autor ‚ pokusu o odfiltrování škodlivého kódu pomocí regexu je marné cvičení.
  • Postup s tečkou vyžaduje absolutní cestu?S relativním nefunguje ‚ nefunguje

Odpovědět

Používám to ve svých skriptech:

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 } 

Mělo by podporovat každou kombinaci znaků, kromě toho, že klíče nemohou mít = v nich, protože to je oddělovač. Funguje cokoli jiného.

% 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 

Je to také zcela bezpečné, protože nepoužívá source nebo eval.

Komentáře

  • Není to ‚ nejprve vytvořit konfigurační soubor, pokud ‚ neexistuje? ‚ t, ale touch -a "${path}" samozřejmě zajistí, že existuje, a to i bez frivolní aktualizace jeho času.

Odpověď

Toto je stručně a bezpečně:

# 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 zajišťuje, že dostanete pouze proměnné z common.vars

Aktualizace: Ilustrace zabezpečení je ta, že

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

Nevytvoří žádné dotčené soubory. Testováno na Macu s bash, tj. pomocí bsd env.

Komentáře

  • Podívejte se, jak se vytvářejí soubory evil1 a evil2, pokud to zadáte do komunikace on.vars „ touch evil1 foo = omg boo = $ (touch evil2)
  • @pihentagy Pro mě následující nevytvoří žádné soubory, kterých se dotknete env -i 'touch evil1 foo=omg boo=$(touch evil2)' . Běží na macu.
  • skutečně, ale nemá přístup k foo. ‚ Zkoušel jsem env -i ... myscript.sh a uvnitř tohoto skriptu foo není definován. Pokud však odstraníte “ odpadky „, bude to fungovat. Takže díky za vysvětlení. : +1:

Odpověď

Většina uživatelů, i když jich není mnoho, již git binární. Proč tedy nepoužívat git config pro správu konfigurace aplikace pomocí vyhrazeného nekonfliktního konfiguračního souboru jako v příkladech níže?

# 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 

Další příkazy najdete v jeho manuálu strana. Je rozumné nejprve se ujistit, že konfigurační soubor existuje:

touch -a ~/.myapp 

Odpovědět

Tenhle se zdá bezpečný a krátký. Neváhejte to bezohledně prolomit. Chtěl bych vědět o lepším způsobu.

TL; DR;

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

Používám bash 4.3.48.

Je také kompatibilní s bash --posix. Test naleznete níže.

Ale sh nepodporuje to kvůli declare.


Základní test pro ty, kteří chtějí důkaz

Vytvořit soubor 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"  

Načtěte konfiguraci pomocí úryvku

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

Výstup (viz sanitizer v akci)

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 

Nyní můžeme zkontrolovat hodnoty

 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" 

Zkontrolovat vedlejší účinky (žádné vedlejší účinky):

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

Příloha. Vyzkoušejte 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 

Odpověď

Podle mého scénáře source nebo . w jako v pořádku, ale chtěl jsem podporovat místní proměnné prostředí (tj. FOO=bar myscript.sh), které mají přednost před konfigurovanými proměnnými. Také jsem chtěl, aby byl konfigurační soubor uživatelsky upravitelný a pohodlný pro někoho, kdo byl zvyklý získávat konfigurační soubory, a aby byl co nejmenší / nejjednodušší, aby to neodvádělo pozornost od hlavního tahu mého velmi malého skriptu.

To je to, s čím jsem přišel:

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) 

V podstatě – kontroluje definice proměnných (aniž by byl velmi flexibilní ohledně mezer) a přepíše tyto řádky tak, aby hodnota se pro tuto proměnnou převede na výchozí hodnotu a proměnná se při úpravě nemodifikuje, podobně jako výše uvedená proměnná XDG_CONFIG_HOME. Poskytuje zdroj této změněné verze konfiguračního souboru a pokračuje dál.

Díky budoucí práci by mohl být skript sed robustnější, odfiltrovat řádky, které vypadají divně nebo jinak. “ Definice atd. se nepřerušují na konci řádku – ale prozatím je to pro mě dost dobré.

Odpovědět

Můžete to udělat:

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

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *