Serialize a variável shell no bash ou zsh

Serialize a variável shell no bash ou zsh

Existe alguma maneira de serializar uma variável shell? Suponha que eu tenha uma variável $VARe queira salvá-la em um arquivo ou algo assim e lê-la novamente mais tarde para obter o mesmo valor de volta.

Existe uma maneira portátil de fazer isso? (Eu não acho)

Existe uma maneira de fazer isso no bash ou zsh?

Responder1

Aviso:Com qualquer uma dessas soluções, você precisa estar ciente de que está confiando na segurança da integridade dos arquivos de dados, pois eles serão executados como código shell em seu script. Protegê-los é fundamental para a segurança do seu script!

Implementação inline simples para serializar uma ou mais variáveis

Sim, tanto no bash quanto no zsh você pode serializar o conteúdo de uma variável de uma maneira que seja fácil de recuperar usando o typesetbuiltin e o -pargumento. O formato de saída é tal que você pode simplesmente usar sourcea saída para recuperar suas coisas.

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

Você pode recuperar suas coisas assim mais tarde em seu script ou em outro script:

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

Isso funcionará para bash, zsh e ksh, incluindo a passagem de dados entre diferentes shells. O Bash traduzirá isso para sua declarefunção interna enquanto o zsh implementa isso, typesetmas como o bash tem um alias para que isso funcione de qualquer maneira, usamos typesetaqui para compatibilidade com o ksh.

Implementação generalizada mais complexa usando funções

A implementação acima é realmente simples, mas se você a chama com frequência, você pode querer criar uma função utilitária para torná-la mais fácil. Além disso, se você tentar incluir os itens acima em funções personalizadas, terá problemas com o escopo das variáveis. Esta versão deve eliminar esses problemas.

Observação para tudo isso, para manter a compatibilidade cruzada bash/zsh, corrigiremos ambos os casos typesete, declareportanto, o código deve funcionar em um ou em ambos os shells. Isso adiciona algum volume e confusão que poderia ser eliminado se você estivesse fazendo isso apenas para um shell ou outro.

O principal problema com o uso de funções para isso (ou incluindo o código em outras funções) é que a typesetfunção gera código que, quando retornado para um script de dentro de uma função, o padrão é criar uma variável local em vez de uma variável global.

Isso pode ser corrigido com um dos vários hacks. Minha tentativa inicial de corrigir isso foi analisar a saída do processo de serialização para sedadicionar o -gsinalizador para que o código criado defina uma variável global quando originado novamente.

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

Observe que a sedexpressão funky deve corresponder apenas à primeira ocorrência de 'typeset' ou 'declare' e adicionar -gcomo primeiro argumento. É necessário corresponder apenas à primeira ocorrência porque, comoStéphane Chazelascorretamente apontado nos comentários, caso contrário, também corresponderá aos casos em que a string serializada contém novas linhas literais seguidas pela palavra declarar ou digitado.

Além de corrigir minha análise inicialgafe, Stéphane tambémsugeridouma maneira menos frágil de hackear isso, que não apenas evita os problemas de análise das strings, mas pode ser um gancho útil para adicionar funcionalidade adicional usando uma função wrapper para redefinir as ações tomadas ao obter os dados de volta. jogar qualquer outro jogo com os comandos declare ou typeset, mas essa técnica seria mais fácil de implementar em uma situação em que você incluísse essa funcionalidade como parte de outra função sua ou não estivesse no controle dos dados que estavam sendo gravados e se ou não teve a -gbandeira adicionada. Algo semelhante também poderia ser feito com aliases, vejaA resposta de Gillespara uma implementação.

Para tornar o resultado ainda mais útil, podemos iterar sobre múltiplas variáveis ​​passadas para nossas funções assumindo que cada palavra na matriz de argumentos é um nome de variável. O resultado fica mais ou menos assim:

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
}

Com qualquer solução, o uso seria assim:

# 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"

Responder2

Use redirecionamento, substituição de comandos e expansão de parâmetros. Aspas duplas são necessárias para preservar espaços em branco e caracteres especiais. O final xsalva as novas linhas finais que, de outra forma, seriam removidas na substituição do comando.

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

Responder3

Serializar tudo – POSIX

Em qualquer shell POSIX, você pode serializar todas as variáveis ​​de ambiente comexport -p. Isso não inclui variáveis ​​de shell não exportadas. A saída é citada corretamente para que você possa lê-la no mesmo shell e obter exatamente os mesmos valores de variáveis. A saída pode não ser legível em outro shell, por exemplo, ksh usa a $'…'sintaxe não POSIX .

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

Serialize alguns ou todos - ksh, bash, zsh

Ksh (ambos pdksh/mksh e ATT ksh), bash e zsh fornecem uma facilidade melhor com otypesetconstruídas em. typeset -pimprime todas as variáveis ​​definidas e seus valores (zsh omite os valores das variáveis ​​que foram ocultadas com typeset -H). A saída contém a declaração adequada para que as variáveis ​​de ambiente sejam exportadas quando lidas (mas se uma variável já tiver sido exportada quando lida, ela não será exportada), para que os arrays sejam lidos como arrays, etc. é citado corretamente, mas só pode ser lido no mesmo shell. Você pode passar um conjunto de variáveis ​​para serializar na linha de comando; se você não passar nenhuma variável, todas serão serializadas.

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

No bash e no zsh, a restauração não pode ser feita a partir de uma função porque typesetas instruções dentro de uma função têm como escopo essa função. Você precisa executar . ./some_varsno contexto onde deseja utilizar os valores das variáveis, tomando cuidado para que as variáveis ​​que eram globais quando exportadas sejam redeclaradas como globais. Se quiser ler os valores dentro de uma função e exportá-los, você pode declarar um alias ou função temporária. Em zsh:

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

No bash (que usa declareem vez de typeset):

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

Em ksh, typesetdeclara variáveis ​​locais em funções definidas com function function_name { … }e variáveis ​​globais em funções definidas com function_name () { … }.

Serialize alguns - POSIX

Se quiser mais controle, você pode exportar o conteúdo de uma variável manualmente. Para imprimir o conteúdo de uma variável exatamente em um arquivo, use o printfbuiltin ( echopossui alguns casos especiais, como echo -nem alguns shells, e adiciona uma nova linha):

printf %s "$VAR" >VAR.content

Você pode ler isso novamente com $(cat VAR.content), exceto que a substituição do comando remove as novas linhas finais. Para evitar esse problema, faça com que a saída nunca termine com uma nova linha.

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

Se quiser imprimir múltiplas variáveis, você pode citá-las com aspas simples e substituir todas as aspas simples incorporadas por '\''. Esta forma de cotação pode ser lida em qualquer shell estilo Bourne/POSIX. O trecho a seguir funciona em qualquer shell POSIX. Ele funciona apenas para variáveis ​​​​de string (e variáveis ​​​​numéricas em shells que as possuem, embora sejam lidas como strings), e não tenta lidar com variáveis ​​​​de array em shells que as possuem.

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
}

Aqui está outra abordagem que não bifurca um subprocesso, mas é mais pesada na manipulação de strings.

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
}

Observe que em shells que permitem variáveis ​​somente leitura, você receberá um erro se tentar ler de volta uma variável somente leitura.

Responder4

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

Outra maneira de fazer isso é garantir que você lide com todas 'as aspas assim:

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

Ou com export:

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

A primeira e a segunda opções funcionam em qualquer shell POSIX, assumindo que o valor da variável não contém a string:

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

A terceira opção deve funcionar para qualquer shell POSIX, mas pode tentar definir outras variáveis, como _ou PWD. A verdade é que as únicas variáveis ​​que ele pode tentar definir são definidas e mantidas pelo próprio shell - e mesmo se você importar exporto valor de qualquer uma delas - como $PWDpor exemplo - o shell simplesmente as redefinirá para o valor correto imediatamente - tente fazer PWD=any_valuee veja por si mesmo.

E porque - pelo menos com o GNU bash- a saída de depuração é automaticamente colocada entre aspas seguras para reinserção no shell, isso funciona independentemente do número de 'aspas duras em "$VAR":

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

$VARposteriormente pode ser definido com o valor salvo em qualquer script no qual o seguinte caminho seja válido com:

. ./VAR.file

informação relacionada