Serializar variable de shell en bash o zsh

Serializar variable de shell en bash o zsh

¿Hay alguna forma de serializar una variable de shell? Supongamos que tengo una variable $VARy quiero poder guardarla en un archivo o lo que sea, y luego volver a leerla más tarde para obtener el mismo valor.

¿Existe una forma portátil de hacer esto? (No me parece)

¿Hay alguna manera de hacerlo en bash o zsh?

Respuesta1

Advertencia:Con cualquiera de estas soluciones, debe tener en cuenta que confía en que la integridad de los archivos de datos esté segura, ya que se ejecutarán como código shell en su secuencia de comandos. ¡Asegurarlos es primordial para la seguridad de su script!

Implementación en línea simple para serializar una o más variables

Sí, tanto en bash como en zsh puedes serializar el contenido de una variable de una manera que sea fácil de recuperar usando la typesetfunción incorporada y el -pargumento. El formato de salida es tal que puedes simplemente enviar sourcela salida para recuperar tus cosas.

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

Puedes recuperar tus cosas así más adelante en tu script o en otro script:

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

Esto funcionará para bash, zsh y ksh, incluido el paso de datos entre diferentes shells. Bash traducirá esto a su declarefunción incorporada mientras zsh lo implementa, typesetpero como bash tiene un alias para que esto funcione de cualquier manera, lo usamos typesetaquí para compatibilidad con ksh.

Implementación generalizada más compleja utilizando funciones.

La implementación anterior es realmente simple, pero si la llama con frecuencia, es posible que desee crear una función de utilidad para hacerlo más fácil. Además, si alguna vez intenta incluir las funciones personalizadas anteriores, tendrá problemas con el alcance de las variables. Esta versión debería eliminar esos problemas.

Tenga en cuenta que para todos estos, para mantener la compatibilidad cruzada de bash/zsh, arreglaremos ambos casos typesety, declarepor lo tanto, el código debería funcionar en uno o ambos shells. Esto agrega algo de volumen y desorden que podría eliminarse si solo hicieras esto para un caparazón u otro.

El principal problema con el uso de funciones para esto (o incluir el código en otras funciones) es que la typesetfunción genera código que, cuando se obtiene de nuevo en un script desde dentro de una función, por defecto crea una variable local en lugar de una global.

Esto se puede solucionar con uno de varios trucos. Mi intento inicial de solucionar este problema fue analizar la salida del proceso de serialización sedpara agregar el -gindicador de modo que el código creado defina una variable global cuando se vuelva a obtener.

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

Tenga en cuenta que la sedexpresión original solo debe coincidir con la primera aparición de 'componer' o 'declarar' y agregarse -gcomo primer argumento. Es necesario hacer coincidir sólo la primera aparición porque, comoStéphane Chazelasseñalado correctamente en los comentarios; de lo contrario, también coincidirá con los casos en los que la cadena serializada contenga nuevas líneas literales seguidas de la palabra declarar o componer.

Además de corregir mi análisis inicialpaso en falso, Stéphane tambiénsugirióuna forma menos frágil de piratear esto que no solo evita los problemas con el análisis de las cadenas, sino que también podría ser un gancho útil para agregar funcionalidad adicional mediante el uso de una función contenedora para redefinir las acciones tomadas al volver a obtener los datos. Esto supone que no jugar cualquier otro juego con los comandos declarar o escribir, pero esta técnica sería más fácil de implementar en una situación en la que incluyera esta funcionalidad como parte de otra función propia o no tuviera el control de los datos que se escriben y si No tenía la -gbandera agregada. También se podría hacer algo similar con los alias, consulteLa respuesta de Gilles.para una implementación.

Para que el resultado sea aún más útil, podemos iterar sobre múltiples variables pasadas a nuestras funciones asumiendo que cada palabra en la matriz de argumentos es un nombre de variable. El resultado se convierte en algo como esto:

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
}

Con cualquiera de las soluciones, el uso se vería así:

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

Respuesta2

Utilice redirección, sustitución de comandos y expansión de parámetros. Se necesitan comillas dobles para preservar los espacios en blanco y los caracteres especiales. El final xguarda las nuevas líneas finales que, de otro modo, se eliminarían en la sustitución del comando.

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

Respuesta3

Serializar todo - POSIX

En cualquier shell POSIX, puede serializar todas las variables de entorno conexport -p. Esto no incluye variables de shell no exportadas. El resultado está entrecomillado correctamente para que pueda volver a leerlo en el mismo shell y obtener exactamente los mismos valores de variable. Es posible que la salida no sea legible en otro shell; por ejemplo, ksh usa la $'…'sintaxis que no es POSIX.

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

Serializar algunos o todos: ksh, bash, zsh

Ksh (tanto pdksh/mksh como ATT ksh), bash y zsh proporcionan una mejor facilidad con latypesetincorporado. typeset -pimprime todas las variables definidas y sus valores (zsh omite los valores de las variables que se han ocultado con typeset -H). La salida contiene una declaración adecuada para que las variables de entorno se exporten cuando se vuelvan a leer (pero si una variable ya se exporta cuando se vuelve a leer, no se exportará), de modo que las matrices se vuelvan a leer como matrices, etc. Aquí también, la salida está citado correctamente pero solo se garantiza que sea legible en el mismo shell. Puede pasar un conjunto de variables para serializar en la línea de comando; Si no pasa ninguna variable, todas se serializan.

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

En bash y zsh, la restauración no se puede realizar desde una función porque typesetlas declaraciones dentro de una función tienen como ámbito esa función. Debe ejecutar . ./some_varsen el contexto donde desea usar los valores de las variables, teniendo cuidado de que las variables que eran globales cuando se exportaron se vuelvan a declarar como globales. Si desea volver a leer los valores dentro de una función y exportarlos, puede declarar un alias o función temporal. En zsh:

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

En bash (que usa declareen lugar de typeset):

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

En ksh, typesetdeclara variables locales en funciones definidas con function function_name { … }y variables globales en funciones definidas con function_name () { … }.

Serializar algunos - POSIX

Si desea tener más control, puede exportar el contenido de una variable manualmente. Para imprimir el contenido de una variable exactamente en un archivo, use el printfincorporado ( echotiene algunos casos especiales, como echo -nen algunos shells, y agrega una nueva línea):

printf %s "$VAR" >VAR.content

Puede volver a leer esto con $(cat VAR.content), excepto que la sustitución del comando elimina las nuevas líneas finales. Para evitar este problema, haga arreglos para que la salida no termine nunca con una nueva línea.

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

Si desea imprimir varias variables, puede citarlas con comillas simples y reemplazar todas las comillas simples incrustadas con '\''. Esta forma de cita se puede volver a leer en cualquier shell estilo Bourne/POSIX. El siguiente fragmento funciona en cualquier shell POSIX. Solo funciona para variables de cadena (y variables numéricas en shells que las tienen, aunque se leerán como cadenas), no intenta manejar variables de matriz en shells que las tienen.

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
}

Aquí hay otro enfoque que no bifurca un subproceso pero es más pesado en la manipulación de cadenas.

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
}

Tenga en cuenta que en shells que permiten variables de solo lectura, obtendrá un error si intenta volver a leer una variable que es de solo lectura.

Respuesta4

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

Otra forma de hacerlo es asegurarse de manejar todas 'las comillas como esta:

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

O con export:

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

La primera y segunda opción funcionan en cualquier shell POSIX, asumiendo que el valor de la variable no contiene la cadena:

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

La tercera opción debería funcionar para cualquier shell POSIX pero puede intentar definir otras variables como _o PWD. Sin embargo, la verdad es que las únicas variables que podría intentar definir las establece y mantiene el propio shell, por lo que incluso si importa exportel valor de cualquiera de ellas, como $PWDpor ejemplo, el shell simplemente las restablecerá a De todos modos, obtenga el valor correcto inmediatamente; intente hacerlo PWD=any_valuey compruébelo usted mismo.

Y debido a que, al menos con GNU bash, la salida de depuración se cita automáticamente de forma segura para volver a ingresarla en el shell, esto funciona independientemente del número de 'comillas en "$VAR":

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

$VARPosteriormente se puede establecer el valor guardado en cualquier script en el que la siguiente ruta sea válida con:

. ./VAR.file

información relacionada