Existe alguma maneira de serializar uma variável de shell? Suponha que eu tenha uma variável $VAR
e que queira salvá-la em um arquivo ou qualquer outra coisa e, em seguida, leia novamente mais tarde para recuperar o mesmo valor?
Existe uma maneira portátil de fazer isso? (Acho que não)
Existe uma maneira de fazer isso no bash ou no zsh?
Respostas:
Aviso: Com qualquer uma dessas soluções, você precisa estar ciente de que está confiando na integridade dos arquivos de dados, pois eles serão executados como código de shell em seu script. Protegê-los é fundamental para a segurança do seu script!
Implementação simples em linha para serializar uma ou mais variáveis
Sim, no bash e no zsh, você pode serializar o conteúdo de uma variável de uma maneira fácil de recuperar usando o
typeset
builtin e o-p
argumento. O formato de saída é tal que você pode simplesmentesource
a saída para recuperar suas coisas.Você pode recuperar suas coisas assim mais tarde em seu script ou em outro script completamente:
Isso funcionará para o bash, zsh e ksh, incluindo a passagem de dados entre diferentes shells. O Bash traduzirá isso para sua
declare
função embutida, enquanto o zsh implementa isso comtypeset
mas o bash tem um alias para que isso funcione de qualquer maneira, pois usamostypeset
aqui para compatibilidade com o ksh.Implementação generalizada mais complexa usando funções
A implementação acima é realmente simples, mas se você chamá-la com frequência, pode querer oferecer uma função de utilitário para facilitar. Além disso, se você tentar incluir as funções personalizadas internas acima, encontrará problemas com o escopo da variável. Esta versão deve eliminar esses problemas.
Observe tudo isso: para manter a compatibilidade cruzada do bash / zsh, corrigiremos os dois casos
typeset
e,declare
portanto, o código deve funcionar em um ou ambos os shells. Isso adiciona algum volume e confusão que poderiam ser eliminados 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 originado em um script de dentro de uma função, o padrão é criar uma variável local em vez de 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
sed
para adicionar o-g
sinalizador, para que o código criado defina uma variável global quando retornado.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, como Stéphane Chazelas apontou corretamente nos comentários, também corresponderá aos casos em que a seqüência de caracteres serializada contenha novas linhas literais seguidas pela palavra declarar ou digitar.Além de corrigir meu falso passo de análise inicial , Stéphane também sugeriu uma maneira menos frágil de hackear isso, que além de solucionar os problemas de análise de seqüências de caracteres, poderia ser um gancho útil para adicionar funcionalidade adicional usando uma função de invólucro para redefinir as ações tomada ao fornecer os dados de volta. Isso pressupõe que você não esteja jogando nenhum 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ê estivesse incluindo essa funcionalidade como parte de outra função própria ou você não estava no controle dos dados que estavam sendo gravados e se o
-g
sinalizador foi adicionado ou não . Algo semelhante também pode ser feito com aliases, consulte a resposta de Gilles para uma implementação.Para tornar o resultado ainda mais útil, podemos iterar várias variáveis passadas para nossas funções assumindo que cada palavra na matriz de argumentos seja um nome de variável. O resultado se torna algo como isto:
Com qualquer solução, o uso ficaria assim:
fonte
declare
é obash
equivalente deksh
'stypeset
.bash
,zsh
também suportetypeset
, nesse sentido,typeset
é mais portátil.export -p
é POSIX, mas não requer nenhum argumento e sua saída depende do shell (embora seja bem especificado para shells POSIX, por exemplo, quando bash ou ksh é chamado comosh
). Lembre-se de citar suas variáveis; usar o operador split + glob aqui não faz sentido.-E
é encontrado apenas em alguns BSDssed
. Os valores variáveis podem conter caracteres de nova linha, portanto,sed 's/^.../.../'
não é garantido que funcione corretamente.a=$'foo\ndeclare bar' bash -c 'declare -p a'
for install produzirá uma linha que começa comdeclare
. Provavelmente é melhor fazerdeclare() { builtin declare -g "$@"; }
antes de telefonarsource
(e desabilitar depois) #shopt -s expandalias
quando não interativo. Com as funções, você também pode aprimorar odeclare
wrapper para restaurar apenas as variáveis especificadas.Use o redirecionamento, substituição de comandos e expansão de parâmetros. São necessárias aspas duplas para preservar espaços em branco e caracteres especiais. O final
x
salva as novas linhas finais, que seriam removidas na substituição de comando.fonte
Serializar tudo - POSIX
Em qualquer shell POSIX, você pode serializar todas as variáveis de ambiente com
export -p
. Isso não inclui variáveis de shell não exportadas. A saída é citada corretamente para que você possa lê-la novamente no mesmo shell e obter exatamente os mesmos valores de variável. A saída pode não ser legível em outro shell, por exemplo, o ksh usa a$'…'
sintaxe não POSIX .Serialize alguns ou todos - ksh, bash, zsh
Ksh (pdksh / mksh e ATT ksh), bash e zsh fornecem uma instalação melhor com o
typeset
builtin.typeset -p
imprime todas as variáveis definidas e seus valores (zsh omite os valores das variáveis que foram ocultadastypeset -H
). A saída contém uma declaração adequada para que as variáveis de ambiente sejam exportadas quando lidas novamente (mas se uma variável já for exportada quando lidas novamente, não será exportada), para que as matrizes sejam lidas novamente como matrizes, etc. Aqui também, a saída é citado corretamente, mas é garantido apenas para leitura 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.No bash e no zsh, a restauração não pode ser feita a partir de uma função porque as
typeset
instruções dentro de uma função têm escopo definido para essa função. Você precisa executar. ./some_vars
no contexto em que deseja usar os valores das variáveis, cuidando para que as variáveis globais quando exportadas sejam declaradas como globais. Se você quiser ler novamente os valores em uma função e exportá-los, poderá declarar um alias ou função temporário. No zsh:No bash (que usa em
declare
vez detypeset
):Em ksh,
typeset
declara variáveis locais em funções definidas comfunction function_name { … }
e variáveis globais em funções definidas comfunction_name () { … }
.Serialize alguns - POSIX
Se você deseja mais controle, 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, comoecho -n
em algumas conchas e adiciona uma nova linha):Você pode ler isso de volta com
$(cat VAR.content)
, exceto que a substituição de comando retira as novas linhas à direita. Para evitar esse enrugamento, organize a saída para nunca terminar com uma nova linha.Se você deseja imprimir várias variáveis, pode citá-las entre aspas simples e substituir todas as aspas simples incorporadas por
'\''
. Essa forma de citação pode ser lida novamente em qualquer shell no estilo Bourne / POSIX. O seguinte trecho funciona em qualquer shell POSIX. Ele funciona apenas para variáveis de string (e variáveis numéricas em shells que as possuem, embora elas sejam lidas como strings), mas não tenta lidar com variáveis de array em shells que as possuem.Aqui está outra abordagem que não bifurca um subprocesso, mas é mais pesada na manipulação de strings.
Observe que nos shells que permitem variáveis somente leitura, você receberá um erro se tentar ler novamente uma variável que é somente leitura.
fonte
$PWD
e$_
- por favor, veja seus próprios comentários abaixo.typeset
um apelido paratypeset -g
?Muito obrigado a @ stéphane-chazelas, que apontou todos os problemas com minhas tentativas anteriores, agora parece funcionar para serializar uma matriz para stdout ou para uma variável.
Essa técnica não analisa shell de entrada (diferente de
declare -a
/declare -p
) e, portanto, é segura contra a inserção mal-intencionada de metacaracteres no texto serializado.Nota: as novas linhas não são escapadas, porque
read
exclui o\<newlines>
par de caracteres; portanto,-d ...
devem ser passadas para leitura e as novas linhas sem escape são preservadas.Tudo isso é gerenciado na
unserialise
função.Dois caracteres mágicos são usados, o separador de campos e o separador de registros (para que várias matrizes possam ser serializadas no mesmo fluxo).
Esses caracteres podem ser definidos como
FS
e,RS
mas nenhum deles pode ser definido comonewline
caractere porque uma nova linha de escape é excluída porread
.O caractere de escape deve ser
\
a barra invertida, pois é isso que é usadoread
para evitar que o caractere seja reconhecido como umIFS
caractere.serialise
serializará"$@"
para stdout,serialise_to
serializará para a variável nomeada em$1
e desserializar com:
ou
por exemplo
(sem uma nova linha à direita)
leia de volta:
ou
O Bash
read
respeita o caractere de escape\
(a menos que você passe o sinalizador -r) para remover um significado especial de caracteres, como separação de campos de entrada ou delimitação de linhas.Se você deseja serializar uma matriz em vez de uma mera lista de argumentos, basta passar sua matriz como a lista de argumentos:
Você pode usar
unserialise
em um loop como fariaread
porque é apenas uma leitura agrupada - mas lembre-se de que o fluxo não é separado por nova linha:fonte
bash
ezsh
renderizá-los como$'\xxx'
. Tente combash -c $'printf "%q\n" "\t"'
oubash -c $'printf "%q\n" "\u0378"'
$IFS
não ser modificada e agora falha ao restaurar os elementos vazios da matriz corretamente. De fato, faria mais sentido usar um valor diferente do IFS e-d ''
evitar o escape da nova linha. Por exemplo, use:
como o separador de campos e apenas escape isso e a barra invertida e useIFS=: read -ad '' array
para importar.read
. backslash-newline forread
é uma maneira de continuar uma linha lógica em outra linha física. Edit: ah eu vejo você mencionar o problema com a nova linha já.Você poderia usar
base64
:fonte
Outra maneira de fazer isso é garantir que você lide com todas as
'
citações físicas como esta:Ou com
export
:A primeira e a segunda opções funcionam em qualquer shell POSIX, assumindo que o valor da variável não contenha a sequência:
A terceira opção deve funcionar para qualquer shell POSIX, mas pode tentar definir outras variáveis como
_
ouPWD
. A verdade é que as únicas variáveis que ele pode tentar definir são definidas e mantidas pelo próprio shell - e, mesmo que você importeexport
o valor de qualquer uma delas - como$PWD
por exemplo -, o shell simplesmente as redefinirá para o valor correto imediatamente de qualquer maneira - tente fazerPWD=any_value
e veja por si mesmo.E porque - pelo menos com os GNU
bash
- a saída de depuração é automaticamente citada com segurança para reinserção no shell, isso funciona independentemente do número de'
aspas rígidas em"$VAR"
:$VAR
posteriormente pode ser definido como o valor salvo em qualquer script no qual o caminho a seguir seja válido:fonte
$$
é o PID do shell em execução, você entendeu errado as citações e significa\$
ou algo assim? A abordagem básica do uso de um documento aqui pode ser feita para funcionar, mas é complicado, e não material de uma linha: o que você escolher como marcador final, terá que escolher algo que não apareça na string.$VAR
contém%
. O terceiro comando nem sempre funciona com valores que contêm várias linhas (mesmo depois de adicionar as aspas duplas obviamente ausentes).env
. Ainda estou curioso para saber o que você quer dizer com várias linhas -sed
exclui todas as linhas até encontrarVAR=
a última - para que todas as linhas$VAR
sejam transmitidas. Você pode fornecer um exemplo que o interrompa?VAR
) não é alteradoPWD
ou_
ou talvez outros que algumas conchas definir. O segundo método requer bash; o formato de saída-v
não é padronizado (nenhum traço, ksh93, mksh e zsh funcionam).Quase o mesmo, mas um pouco diferente:
Do seu script:
Este tempo acima é testado.
fonte
'
,*
, etc.echo "$LVALUE=\"$RVALUE\""
deve manter as novas linhas também e o resultado no arquivo cfg_file deve ser o seguinte: MY_VAR1 = "Linha1 \ nLinha 2" Portanto, quando avaliar MY_VAR1, também conterá as novas linhas. Claro que você pode ter problemas se o seu valor armazenado contiver um"
caractere. Mas isso também pode ser resolvido.