Kabuk değişkenini bash veya zsh olarak seri hale getirme


12

Bir kabuk değişkenini serileştirmenin herhangi bir yolu var mı? Bir değişkenim olduğunu varsayalım $VARve bunu bir dosyaya veya her neyse kaydedebiliyorum ve sonra aynı değeri geri almak için daha sonra tekrar okumak istiyorum.

Bunu yapmanın taşınabilir bir yolu var mı? (Ben öyle düşünmüyorum)

Bunu bash veya zsh ile yapmanın bir yolu var mı?


2
Dikkat: Geçen gün kabul ettiğim yanıtın sürümünde bazı senaryolarda kırılacak ciddi bir sorun vardı. Düzeltmeleri dahil etmek için yeniden yazdım (ve özellikler ekleyin) ve gerçekten sıfırdan yeniden okumalı ve sabit sürümü kullanmak için kodunuzu port etmelisiniz.
Caleb

^ Başka bir ^ @ Caleb'in dik vatandaşlığı örneği.
mikeserv

Yanıtlar:


14

Uyarı: Bu çözümlerden herhangi birinde, komut dosyasında kabuk kodu olarak yürütülecekleri için veri dosyalarının bütünlüğünün güvende olduğuna güvendiğinizi bilmeniz gerekir. Onları güvence altına almak, komut dosyanızın güvenliği için çok önemlidir!

Bir veya daha fazla değişkeni serileştirmek için basit satır içi uygulama

Evet, hem bash hem de zsh'de, bir değişkenin içeriğini typesetyerleşik ve -pargümanı kullanarak alması kolay bir şekilde serileştirebilirsiniz . Çıktı biçimi, sourceçıktılarınızı geri almak için çıktıyı kolayca alabileceğiniz şekildedir.

 # You have variable(s) $FOO and $BAR already with your stuff
 typeset -p FOO BAR > ./serialized_data.sh

Eşyalarınızı daha sonra komut dosyanızda veya başka bir komut dosyasında tamamen geri alabilirsiniz:

# Load up the serialized data back into the current shell
source serialized_data.sh

Bu, farklı kabuklar arasında veri aktarımı da dahil olmak üzere bash, zsh ve ksh için çalışacaktır. Bash bunu yerleşik declareişlevine çevirecek , zsh ise bunu uygulayacak typesetancak typesetbash'ın burada ksh uyumluluğu için kullandığımız her iki şekilde çalışması için bir takma adı olduğu için .

İşlevleri kullanarak daha karmaşık genelleştirilmiş uygulama

Yukarıdaki uygulama gerçekten basittir, ancak sık sık ararsanız, bunu kolaylaştırmak için kendinize bir yardımcı program işlevi vermek isteyebilirsiniz. Ayrıca, yukarıdaki özel işlevlerin içine dahil etmeye çalışırsanız, değişken kapsam belirleme sorunlarıyla karşılaşırsınız. Bu sürüm bu sorunları ortadan kaldırmalıdır.

Bunların hepsine dikkat edin, bash / zsh çapraz uyumluluğunu korumak için her iki durumu da düzelteceğiz typesetve declarebu nedenle kodun kabuklardan birinde veya her ikisinde de çalışması gerekir. Bu, yalnızca bir kabuk veya diğeri için bunu yapıyorsanız ortadan kaldırılabilecek bazı toplu ve karışıklık ekler.

Bunun için işlevlerin kullanılmasıyla ilgili ana sorun (veya kodu diğer işlevlere dahil etmek), typesetişlevin, bir işlevin içinden bir komut dosyasına geri kaynaklandığında, genel bir değişken yerine yerel bir değişken oluşturmayı varsayılan olarak kod üretmesidir.

Bu, birkaç hack'ten biriyle düzeltilebilir. Bunu düzeltmek için ilk denemem , oluşturulan kod geri kaynaklandığında genel bir değişkeni tanımlamak sediçin -gbayrağı eklemek için serialize sürecinin çıktısını ayrıştırmaktı .

serialize() {
    typeset -p "$1" | sed -E '0,/^(typeset|declare)/{s/ / -g /}' > "./serialized_$1.sh"
}
deserialize() {
    source "./serialized_$1.sh"
}

Korkak sedifadenin yalnızca 'yazı kümesi' veya 'deklarasyon' kelimelerinin ilk oluşumuyla eşleşeceğini ve -gilk argüman olarak ekleneceğini unutmayın . Stéphane Chazelas'ın yorumlarda doğru bir şekilde işaret ettiği için sadece ilk tekrarla eşleşmek gerekir, aksi takdirde serileştirilmiş dizenin, kelimenin tam anlamıyla yeni satırlar içerdiği ve ardından bildiri veya yazı kümesi içerdiği durumlarla da eşleşir.

İlk ayrıştırma sahte pas'ımı düzeltmenin yanı sıra , Stéphane de bunu kesmek için daha az kırılgan bir yol önerdi , bu da sadece dizeleri ayrıştırmayla ilgili sorunları atmakla kalmadı, aynı zamanda eylemleri yeniden tanımlamak için bir sarma işlevi kullanarak ek işlevsellik eklemek için yararlı bir kanca olabilir. Bu, beyan veya dizgi komutlarıyla başka bir oyun oynamadığınızı varsayar, ancak bu tekniğin kendi işlevinizin bir parçası olarak veya bu işlevi eklediğiniz bir durumda uygulanması daha kolay olacaktır. yazılan verilerin ve -gbayrağın eklenip eklenmediğinin kontrolü sizde değildi . Takma adlarla da benzer bir şey yapılabilir, bkz. Gilles'in bir uygulama cevabı .

Sonucu daha da kullanışlı hale getirmek için, bağımsız değişken dizisindeki her kelimenin bir değişken adı olduğunu varsayarak işlevlerimize aktarılan birden çok değişken üzerinde yineleme yapabiliriz. Sonuç şöyle olur:

serialize() {
    for var in $@; do
        typeset -p "$var" > "./serialized_$var.sh"
    done
}

deserialize() {
    declare() { builtin declare -g "$@"; }
    typeset() { builtin typeset -g "$@"; }
    for var in $@; do
        source "./serialized_$var.sh"
    done
    unset -f declare typeset
}

Her iki çözümde de kullanım şöyle görünür:

# Load some test data into variables
FOO=(an array or something)
BAR=$(uptime)

# Save it out to our serialized data files
serialize FOO BAR

# For testing purposes unset the variables to we know if it worked
unset FOO BAR

# Load  the data back in from out data files
deserialize FOO BAR

echo "FOO: $FOO\nBAR: $BAR"

declareolduğu basheşdeğer ksh's typeset. bash, zshayrıca destek typesetbu konuda, typesetdaha taşınabilir. export -pPOSIX, ancak herhangi bir bağımsız değişken almaz ve çıktısı kabuğa bağlıdır (POSIX kabukları için iyi belirlenmiş olsa da, örneğin bash veya ksh olarak adlandırıldığında sh). Değişkenlerinizi alıntılamayı unutmayın; split + glob operatörünü burada kullanmak mantıklı değil.
Stéphane Chazelas

Bunun -Eyalnızca bazı BSD'lerde bulunduğunu unutmayın sed. Değişken değerler yeni satır karakterleri içerebilir, bu nedenle sed 's/^.../.../'düzgün çalışacağı garanti edilmez.
Stéphane Chazelas

Tam da aradığım şey buydu! Değişkenleri mermiler arasında ileri geri itmek için uygun bir yol istedim.
fwenom

Demek istediğim: a=$'foo\ndeclare bar' bash -c 'declare -p a'for install ile başlayan bir satır çıkacaktır declare. Muhtemelen declare() { builtin declare -g "$@"; }aramadan önce yapmak daha iyidir source(ve daha sonra unset)
Stéphane Chazelas

2
@Gilles, takma adlar işlevlerin içinde çalışmaz (işlev tanımı sırasında tanımlanması gerekir) ve bash ile shopt -s expandaliasetkileşimli olmadığında bir şey yapmanız gerektiği anlamına gelir . İşlevlerle, declaresarıcıyı yalnızca belirlediğiniz değişkenleri geri yükleyecek şekilde geliştirebilirsiniz .
Stéphane Chazelas

3

Yeniden yönlendirme, komut değiştirme ve parametre genişletmeyi kullanın. Boşluğu ve özel karakterleri korumak için çift tırnak işareti gerekir. Sondaki x, komut ikamesinde aksi takdirde kaldırılacak olan son satırları kaydeder.

#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}

Muhtemelen değişken adını da dosyaya kaydetmek istiyor.
user80551

2

Tümünü seri hale getir - POSIX

Herhangi bir POSIX kabuğunda, tüm ortam değişkenlerini ile serileştirebilirsiniz export -p. Bu, dışa aktarılmayan kabuk değişkenlerini içermez. Çıktı düzgün şekilde alıntılanır, böylece onu aynı kabukta okuyabilir ve tam olarak aynı değişken değerlerini alabilirsiniz. Çıktı başka bir kabukta okunamayabilir, örneğin ksh POSIX olmayan $'…'sözdizimini kullanır .

save_environment () {
  export -p >my_environment
}
restore_environment () {
  . ./my_environment
}

Bazılarını veya tümünü seri halinde ksh, bash, zsh

Ksh (pdksh / mksh ve ATT ksh), bash ve zsh typesetyerleşik ile daha iyi bir tesis sağlar . typeset -ptanımlı tüm değişkenleri ve değerlerini yazdırır (zsh, gizlenmiş değişkenlerin değerlerini atlar typeset -H). Çıktı uygun bildirimi içerdiğinden, ortam değişkenleri geri okunduğunda dışa aktarılır (ancak bir değişken geri okunduğunda zaten dışa aktarılırsa, dışa aktarılmaz), böylece diziler diziler vb. Olarak okunur. uygun şekilde alıntılanır, ancak yalnızca aynı kabukta okunabileceği garanti edilir. Serileştirilecek bir dizi değişkeni komut satırında iletebilirsiniz; herhangi bir değişkeni geçmezseniz hepsi serileştirilir.

save_some_variables () {
  typeset -p VAR OTHER_VAR >some_vars
}

Bash ve zsh dosyalarında geri yükleme bir işlevden yapılamaz çünkü typesetbir işlev içindeki ifadeler bu işleve dahil edilir. . ./some_varsDışa aktarıldığında global olan değişkenlerin global olarak yeniden tanımlanacağına dikkat ederek, değişkenlerin değerlerini kullanmak istediğiniz bağlamda çalıştırmanız gerekir . Bir işlev içindeki değerleri tekrar okumak ve dışa aktarmak istiyorsanız, geçici bir takma ad veya işlev bildirebilirsiniz. Zsh dilinde:

restore_and_make_all_global () {
  alias typeset='typeset -g'
  . ./some_vars
  unalias typeset
}

Bash içinde ( declareyerine kullanır typeset):

restore_and_make_all_global () {
  alias declare='declare -g'
  shopt -s expand_aliases
  . ./some_vars
  unalias declare
}

Ksh, typesetile tanımlanan işlevlerde yerel değişkenleri ve ile tanımlanan işlevlerde function function_name { … }genel değişkenleri bildirir function_name () { … }.

Bazılarını seri hale getir - POSIX

Daha fazla kontrol istiyorsanız, bir değişkenin içeriğini manuel olarak dışa aktarabilirsiniz. Bir değişkenin içeriğini tam olarak bir dosyaya yazdırmak için printfyerleşik'i kullanın ( bazı kabuklarda echoolduğu gibi birkaç özel durum vardır echo -nve yeni satır ekler):

printf %s "$VAR" >VAR.content

$(cat VAR.content)Komut yerine koymanın sondaki satırsonlarını kaldırması dışında bunu ile tekrar okuyabilirsiniz . Bu kırışıklığı önlemek için, çıktının bir satırsonu ile bitmemesini sağlayın.

VAR=$(cat VAR.content && echo a)
if [ $? -ne 0 ]; then echo 1>&2 "Error reading back VAR"; exit 2; fi
VAR=${VAR%?}

Birden çok değişkeni yazdırmak istiyorsanız, bunları tek tırnak işareti ile tırnak içine alabilir ve katıştırılmış tek tırnakları ile değiştirebilirsiniz '\''. Bu alıntılama yöntemi herhangi bir Bourne / POSIX tarzı kabuğa geri okunabilir. Aşağıdaki snippet herhangi bir POSIX kabuğunda çalışır. Yalnızca dize değişkenleri (ve bunları içeren kabuklardaki sayısal değişkenler için çalışır, ancak dize olarak okunacak olsalar da), bunları içeren kabuklardaki dizi değişkenleriyle uğraşmaya çalışmaz.

serialize_variables () {
  for __serialize_variables_x do
    eval "printf $__serialize_variables_x=\\'%s\\'\\\\n \"\$${__serialize_variables_x}\"" |
    sed -e "s/'/'\\\\''/g" -e '1 s/=.../=/' -e '$ s/...$//'
  done
}

İşte bir alt süreci çatallamayan, ancak dize manipülasyonunda daha ağır olan başka bir yaklaşım.

serialize_variables () {
  for __serialize_variables_var do
    eval "__serialize_variables_tail=\${$__serialize_variables_var}"
    while __serialize_variables_quoted="$__serialize_variables_quoted${__serialize_variables_tail%%\'*}"
          [ "${__serialize_variables_tail%%\'*}" != "$__serialize_variables_tail" ]; do
      __serialize_variables_tail="${__serialize_variables_tail#*\'}"
      __serialize_variables_quoted="${__serialize_variables_quoted}'\\''"
    done
    printf "$__serialize_variables_var='%s'\n" "$__serialize_variables_quoted"
  done
}

Salt okunur değişkenlere izin veren kabuklarda, salt okunur bir değişkeni tekrar okumaya çalıştığınızda hata alacağınızı unutmayın.


Bu gibi değişkenlerde getiriyor $PWDve $_- aşağıda kendi yorumlarınızı lütfen bkz.
mikeserv

@Caleb typesetTakma ad oluşturmaya ne dersiniz typeset -g?
Gilles 'SO- kötü olmayı kes'

@Gilles, Stephanie'nin fonksiyon yöntemini önerdikten sonra bunu düşündüm, ancak kabuklar arasında gerekli takma ad genişletme seçeneklerini portatif olarak nasıl ayarlayacağımdan emin değildim. Belki de bunu benim dahil ettiğim fonksiyona uygulanabilir bir alternatif olarak cevabınıza koyabilirsiniz.
Caleb

0

Şunları kullanabilirsiniz base64:

$ VAR="1/ 
,x"
$ echo "$VAR" | base64 > f
$ VAR=$(cat f | base64 -d)
$ echo "${VAR}X"
1/ 
,xX

-2
printf 'VAR=$(cat <<\'$$VAR$$'\n%s\n'$$VAR$$'\n)' "$VAR" >./VAR.file

Bunu yapmanın başka bir yolu, 'bu gibi tüm sert tırnakları ele almanızı sağlamaktır :

sed '"s/'"'/&"&"&/g;H;1h;$!d;g;'"s/.*/VAR='&'/" <<$$VAR$$ >./VAR.file
$VAR
$$VAR$$

Veya export:

env - "VAR=$VAR" sh -c 'export -p' >./VAR.file 

Birinci ve ikinci seçenekler, değişkenin değerinin dizeyi içermediğini varsayarak herhangi bir POSIX kabuğunda çalışır:

"\n${CURRENT_SHELLS_PID}VAR${CURRENT_SHELLS_PID}\n" 

Üçüncü seçenek herhangi bir POSIX kabuğu için çalışmalıdır, ancak _veya gibi diğer değişkenleri tanımlamaya çalışabilir PWD. Gerçek şu ki, tanımlamaya çalışabileceği tek değişkenler kabuğun kendisi tarafından ayarlanır ve korunur - ve böylece exportbunlardan herhangi biri için içe aktarma değeri yapsanız bile - örneğin $PWD, kabuk bunları basitçe sıfırlar hemen doğru değeri hemen - yapmaya çalışın PWD=any_valueve kendiniz görün.

Ve - en azından GNU'lar ile bash- hata ayıklama çıktısı, kabuğa yeniden giriş için otomatik olarak güvenli bir şekilde alıntılandığından, bu, içindeki 'sert tırnak sayısından bağımsız olarak çalışır "$VAR":

 PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file

$VAR daha sonra aşağıdaki yolun geçerli olduğu herhangi bir komut dosyasında kaydedilen değere ayarlanabilir:

. ./VAR.file

İlk komutta ne yazmaya çalıştığınızdan emin değilim. $$kabuk PID nedir, alıntı yanlış ve ortalama \$ya da bir şey aldın mı? Burada bir belgeyi kullanmanın temel yaklaşımı işe yarayabilir, ancak tek katmanlı bir malzeme değil, zor: son işaretçi olarak ne seçerseniz seçin, dizede görünmeyen bir şey seçmeniz gerekir.
Gilles 'SO- kötü olmayı kes'

İkinci komut $VARiçerdiğinde çalışmaz %. Üçüncü komut her zaman birden fazla satır içeren değerlerle çalışmaz (açıkça eksik çift tırnak ekledikten sonra bile).
Gilles 'SO- kötü olmayı kes'

@Gilles - Ben onun pid biliyorum - Ben benzersiz bir sınırlayıcı ayarlamak için basit bir kaynak olarak kullandım. "Her zaman değil" ile tam olarak ne demek istiyorsun? Ve hangi çift tırnak eksik eksik anlamıyorum - bunların hepsi değişken atamalar. Çifte alıntılar sadece bu bağlamdaki durumu karıştırır.
mikeserv

@Gilles - Ödev şeyi geri çekiyorum - bu bir argüman env. Birden çok satır hakkında ne demek istediğinizi hala merak ediyorum - sedsonuna kadar karşılaşana VAR=kadar her satırı siler - böylece tüm satırlar $VARaktarılır. Lütfen onu kıran bir örnek verebilir misiniz?
mikeserv

Ah, özür dileriz, üçüncü yöntem işe yarar (alıntı düzeltmesi ile). Eh, (burada değişken adı varsayarak VAR) değiştirilmez PWDveya _bazı kabukları tanımlamak belki başkalarını veya. İkinci yöntem bash gerektirir; çıkış biçimi -vstandart değil (tire, ksh93, mksh ve zsh öğelerinin hiçbiri çalışmaz).
Gilles 'SO- kötü olmayı bırak'

-2

Neredeyse aynı ama biraz farklı:

Senaryonuzdan:

#!/usr/bin/ksh 

save_var()
{

    (for ITEM in $*
    do
        LVALUE='${'${ITEM}'}'
        eval RVALUE="$LVALUE"
        echo "$ITEM=\"$RVALUE\""  
    done) >> $cfg_file
}

restore_vars()
{
    . $cfg_file
}

cfg_file=config_file
MY_VAR1="Test value 1"
MY_VAR2="Test 
value 2"

save_var MY_VAR1 MY_VAR2
MY_VAR1=""
MY_VAR2=""

restore_vars 

echo "$MY_VAR1"
echo "$MY_VAR2"

Yukarıdaki bu zaman test edilmiştir.


Test etmediğini görebiliyorum! Çekirdek mantık işe yarıyor, ama bu zor bir şey değil. Zor olan şey, doğru bir şekilde alıntı yapmaktır ve bunlardan hiçbirini yapmıyorsunuzdur. Değerleri yeni satırları içeren değişkenler deneyin ', *vb
Gilles 'SO dur olma kötülük'

echo "$LVALUE=\"$RVALUE\""yeni satırları da tutması gerekiyor ve cfg_file'daki sonuç şöyle olmalıdır: MY_VAR1 = "Line1 \ nLine 2" Böylece MY_VAR1'i değerlendirirken yeni satırları da içerecektir. Tabii ki saklı değeriniz "char içeriyorsa sorun yaşayabilirsiniz . Ancak bu da dikkat edilebilir.
vadimbog

1
Btw, neden burada sorulan soruyu doğru cevaplayan bir şeyi aşağı oylama? Yukarıda benim için çok iyi çalışıyor ve senaryolarımda her yerde kullanmak?
vadimbog
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.