Existe alguma maneira de serializar uma variável shell? Suponha que eu tenha uma variável $VAR
e 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 typeset
builtin e o -p
argumento. O formato de saída é tal que você pode simplesmente usar source
a 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 declare
função interna enquanto o zsh implementa isso, typeset
mas como o bash tem um alias para que isso funcione de qualquer maneira, usamos typeset
aqui 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 typeset
e, declare
portanto, 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 typeset
funçã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 sed
adicionar o -g
sinalizador 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 sed
expressão funky deve corresponder apenas à primeira ocorrência de 'typeset' ou 'declare' e adicionar -g
como 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 -g
bandeira 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 x
salva 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 otypeset
construídas em. typeset -p
imprime 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 typeset
as instruções dentro de uma função têm como escopo essa função. Você precisa executar . ./some_vars
no 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 declare
em vez de typeset
):
restore_and_make_all_global () {
alias declare='declare -g'
shopt -s expand_aliases
. ./some_vars
unalias declare
}
Em ksh, typeset
declara 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 printf
builtin ( echo
possui alguns casos especiais, como echo -n
em 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 export
o valor de qualquer uma delas - como $PWD
por exemplo - o shell simplesmente as redefinirá para o valor correto imediatamente - tente fazer PWD=any_value
e 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
$VAR
posteriormente pode ser definido com o valor salvo em qualquer script no qual o seguinte caminho seja válido com:
. ./VAR.file