Shell betiğim için config dosyasını kullan


26

Kendi betiğim için bir yapılandırma dosyası oluşturmam gerekiyor: burada bir örnek:

senaryo:

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

İçeriği /home/myuser/test/config:

nam="Mark"
sur="Brown"

bu işe yarıyor!

Sorum şu: Bunu yapmanın doğru yolu mu, yoksa başka yolları var mı?


Değişkenler en üstte olmalıdır. İşe yaramasına şaşırdım. Neyse, neden bir yapılandırma dosyasına ihtiyacınız var? Bu değişkenleri başka bir yerde kullanmayı mı düşünüyorsunuz?
Faheem Mitha,

Faheem, değişkenlere ihtiyacım var çünkü betiğimde birçok seçenek var: bir config dosyası kullanmak betiği basitleştirecek. Teşekkürler
Pol Hallen

5
IMHO iyi. Bu şekilde yapardım.
Tinti

abcdeayrıca bu şekilde yapar ve bu oldukça büyük bir programdır (bir kabuk betiği için). Burada bir göz atabilirsiniz .
Lucas

Yanıtlar:


19

sourceisteğe bağlı kod yürüteceğinden güvenli değildir. Bu sizin için bir sorun olmayabilir, ancak dosya izinleri yanlışsa, dosya sistemi erişimi olan bir saldırganın, başka bir güvenlikli komut dosyası tarafından yüklenen bir yapılandırma dosyasına kod enjekte ederek ayrıcalıklı bir kullanıcı olarak kod yürütmesi mümkün olabilir. init betiği.

Şimdiye kadar tanımlayabildiğim en iyi çözüm, beceriksiz yeniden keşif çözümü:

myscript.conf

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

Kullanarak source, echo rm -rf /çalışan kullanıcının değiştirmesinin yanı sıra, iki kez de çalışır $PROMPT_COMMAND. Bunun yerine, şunu yapın:

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 uyumlu)

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

Kodumda bir güvenlik açığı bulursanız lütfen yanıtlayın.


1
FYI, ne yazık ki Apple tarafından dayatılan çılgınca lisanslama sorunlarına tabi olan ve Mac’lerde varsayılan olarak bulunmayan bir Bash sürüm 4.0 çözümüdür
Sukima

@Sukima İyi nokta. Bash 3 ile uyumlu bir sürüm ekledim. Zafiyeti *, girdilerde doğru bir şekilde işlememesidir, ancak Bash'de bu karakteri iyi yapan ne?
Mikkel

Parola ters eğik çizgi içeriyorsa ilk komut dosyası başarısız olur.
Kusalananda

@Kusalananda Ters eğik çizgi kaçarsa? my\\password
Mikkel

10

Hem Mac hem de Linux'ta Bash 3 ve üstü ile uyumlu temiz ve taşınabilir bir sürüm.

Tüm kabuk komut dosyalarınızda büyük, darmadağın, çoğaltılmış "varsayılanlar" config işlevine gerek kalmaması için tüm varsayılanları ayrı bir dosyada belirtir. Ayrıca varsayılan geri dönüşlerle veya geri dönüş olmadan okuma arasında seçim yapmanıza olanak sağlar:

config.cfg :

myvar=Hello World

config.cfg.defaults :

myvar=Default Value
othervar=Another Variable

config.shlib (bu bir kütüphanedir, yani shebang-line yoktur):

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 (veya config değerlerini okumak istediğiniz komut dosyaları) :

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

Test komut dosyasının açıklaması:

  • Test.sh içindeki config_get'ın tüm kullanımlarının çift tırnak içine alındığını unutmayın . Her config_get dosyasını çift tırnak içine alarak değişken değerindeki metnin hiçbir zaman bayraklar olarak yanlış yorumlanmamasını sağlıyoruz. Konfigürasyon değerinde bir satırdaki çoklu boşluklar gibi boşlukları gerektiği gibi korumamızı sağlar.
  • Ve bu printfçizgi nedir? Dikkat etmeniz gereken bir şey var: echoüzerinde kontrol sahibi olmadığınız metinleri yazdırmak için kötü bir komuttur. Çift tırnak kullanıyor olsanız bile, bayrakları yorumlayacaktır. Belirlemeyi deneyin myvar(içinde config.cfgkadar) -eve bu nedenle, bir boş satır göreceksiniz echobu bir bayrak olduğunu düşünecektir. Ancak printfbu sorun yok. printf --"Bu baskı ve bayrakları gibi bir şey yorumlamak değil" diyor ve "%s\n"bir eğik satırsonu içeren bir dize olarak çıktı biçimlendirmek" diyor ve son olarak nihai parametre formatına printf için değerdir.
  • Eğer değerleri ekrana yansıtmayacaksanız, onları normal şekilde atamanız yeterli myvar="$(config_get myvar)";. Bunları ekrana yazdıracaksanız, kullanıcı config içinde olabilecek herhangi bir yankı uyumlu dizeye karşı tamamen güvenli olması için printf kullanmanızı öneririm. Kullanıcı tarafından sağlanan değişken Ama eğer yankı gayet değil o "bayrak" yorumlanabilir tek durum olduğundan bir şey gibi pek, sen dile getirdiği de dizesinin ilk karakter echo "foo: $(config_get myvar)";"foo" çünkü güvenlidir gelmez bir çizgi ile başlayın ve bu nedenle dize geri kalanının da bunun için bayrak olmadığını söyler. :-)

@ user2993656 Özgün kodumun hala içinde özel yapılandırma dosya adımı (ortam.cfg) bulunduğunu belirlediğiniz için teşekkür ederiz. Yaptığınız "echo -n" düzenlemesine gelince, bu kullanılan kabuğa bağlıdır. Mac / Linux Bash'de "echo -n", yeni satırları takip etmekten kaçınmak için yaptığım "yeni satırı takip etmeden yankı" anlamına gelir. Ancak onsuz aynı şekilde çalışıyor gibi görünüyor, bu yüzden düzenlemeler için teşekkürler!
gw0

Aslında, ben sadece daha önce tekrar okudum ve yeniden yazdım, ecf yerine printf'i kullandım.
gw0

Bu sürümü gerçekten beğendim. config.cfg.defaultsArama sırasında onları tanımlamak yerine onları düşürdüm $(config_get var_name "default_value"). tritarget.org/static/…
Sukima

7

Yapılandırma dosyasını ayrıştırın, çalıştırmayın.

Şu anda işyerinde son derece basit bir XML yapılandırması kullanan bir uygulama yazıyorum:

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

Kabuk komut dosyasında ("uygulama"), kullanıcı adına ulaşmak için yaptığım şey budur (az ya da çok, bir kabuk işlevine koydum):

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

xmlKomuttur XMLStarlet en Unix'lerde için kullanılabilir.

Uygulamanın diğer bölümleri de XML dosyalarında kodlanmış verilerle ilgilendiği için XML kullanıyorum, bu yüzden en kolayıydı.

JSON'u tercih ederseniz, jqkullanımı kolay olan bir kabuk JSON ayrıştırıcısı vardır.

Konfigürasyon dosyam JSON'da şöyle görünecek:

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

Ve sonra senaryodaki kullanıcı adını alacağım:

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

Senaryoyu yürütmenin bir takım avantajları ve dezavantajları vardır. Başlıca dezavantajları güvenlik, eğer birileri config dosyasını değiştirebiliyorsa, kod yürütebilirler ve aptal kanıtı elde etmek daha zordur. Avantajları hızdır, basit bir testte, pq çalıştırmaya göre bir config dosyası oluşturmak için 10.000 kat daha hızlıdır ve esneklik, maymun yama pitonunu seven herkes bunu takdir edecektir.
icarus

@icarus Genelde ne kadar büyük yapılandırma dosyalarıyla karşılaşıyorsunuz ve bunları bir oturumda ne sıklıkla ayrıştırmanız gerekiyor? Bir seferde birkaç değerin XML veya JSON dışında olabileceğine dikkat edin.
Kusalananda

Genellikle sadece birkaç (1 ila 3) değer. Birden evalfazla değer ayarlamak için kullanıyorsanız , config dosyasının :-) seçilmiş bölümlerini çalıştırıyorsunuzdur.
icarus

1
@icarus Dizileri düşünüyordum ... Hiçbir evalşeye gerek yok. Mevcut bir ayrıştırıcı ile standart bir format kullanmanın (harici bir yardımcı program olsa da) performansa dayanımı, sağlamlık, kod miktarı, kullanım kolaylığı ve bakım kolaylığı ile karşılaştırıldığında önemsizdir.
Kusalananda

1
"Yapılandırma dosyasını çözümleme, çalıştırma" için +1
Iiridayn

4

En yaygın, verimli ve doğru yol kullanmak sourceveya .bir kestirme şeklidir. Örneğin:

source /home/myuser/test/config

veya

. /home/myuser/test/config

Bununla birlikte, göz önünde bulundurulması gereken bir husus, ek kod eklenebilmesi koşuluyla, harici kaynaklı ek bir yapılandırma dosyası kullanmanın artabileceği güvenlik sorunlarıdır. Bu sorunun nasıl tespit edilip çözüleceği de dahil olmak üzere daha fazla bilgi için, http://wiki.bash-hackers.org/howto/conffile#secure_it adresindeki 'Onu güvenli hale getirin' bölümüne bir göz atmanızı öneririm


5
Bu makale için büyük umutlarım vardı (arama sonuçlarıma da geldi), ancak yazarın kötü niyetli kodları filtrelemek için regex kullanmaya çalışılması önerisi boşuna bir alıştırma.
Mikkel

Nokta ile prosedür, mutlak bir yol gerektirir? Göreceli olan o işe yaramazsa
Davide

2

Bunu komut dosyalarımda kullanırım:

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
}

Her karakter kombinasyonunu desteklemesi =gerekir, çünkü tuşların içinde bulunamaz , çünkü ayırıcı budur. Başka bir şey çalışıyor.

% 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

Ayrıca, bu tamamen güvenlidir çünkü source/eval


0

Senaryom için sourceya .da iyiydim, ancak FOO=bar myscript.shyapılandırılmış değişkenlerden öncelikli olarak yerel çevre değişkenlerini (yani ) desteklemek istedim . Ayrıca config dosyasının kullanıcı tarafından düzenlenebilir ve rahat bir şekilde yapılandırılmış olmasını ve yapılandırma dosyalarının kaynaklanmasında rahat olmasını ve çok küçük betiğimin ana itişinden rahatsız olmamalarını mümkün olduğunca küçük / basit tutmasını istedim.

Ben de öyle geldim:

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)

Temel olarak - değişken tanımlarını kontrol eder (boşluklar konusunda çok esnek olmadan) ve bu satırları yeniden yazar, böylece değer bu değişken için bir varsayılan değere dönüştürülür ve değişken, XDG_CONFIG_HOMEyukarıdaki değişken gibi saptanırsa değiştirilmez . Bu config dosyasının değiştirilmiş versiyonunu sağlar ve devam eder.

Gelecekteki çalışma sedsenaryoyu daha sağlam hale getirebilir, garip görünen veya tanımları olmayan satırları filtrelendirebilir, satır yorumlarını bozmaz - ama bu benim için yeterince iyi.


0

Bu özlü ve güvenlidir:

# 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`)

-iOlmasını sağlar sadece değişkenleri olsuncommon.vars


-2

Bunu yapabilirsin:

#!/bin/bash
name="mohsen"
age=35
cat > /home/myuser/test/config << EOF
Name=$name
Age=$age
EOF
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.