¿Hay alguna forma de serializar una variable de shell? Supongamos que tengo una variable $VAR
, y quiero poder guardarla en un archivo o lo que sea, y luego leerla más tarde para recuperar el mismo valor.
¿Hay alguna forma portátil de hacer esto? (No lo creo)
¿Hay alguna manera de hacerlo en bash o zsh?
Respuestas:
Advertencia: con cualquiera de estas soluciones, debe tener en cuenta que confía en la integridad de los archivos de datos para que estén seguros, 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 puede serializar el contenido de una variable de una manera fácil de recuperar utilizando
typeset
el-p
argumento y el argumento incorporado . El formato de salida es tal que puede simplementesource
la salida para recuperar sus cosas.Puede recuperar sus cosas así más adelante en su secuencia de comandos o en otra secuencia de comandos por completo:
Esto funcionará para bash, zsh y ksh, incluido el paso de datos entre diferentes shells. Bash traducirá esto a su
declare
función incorporada mientras que zsh implementa esto,typeset
pero como bash tiene un alias para que esto funcione de cualquier manera, ya que lo usamostypeset
aquí para la compatibilidad de ksh.Implementación generalizada más compleja usando funciones
La implementación anterior es realmente simple, pero si la llama con frecuencia, es posible que desee tener una función de utilidad para que sea más fácil. Además, si alguna vez intenta incluir las funciones personalizadas anteriores, se encontrará con problemas con el alcance variable. Esta versión debería eliminar esos problemas.
Tenga en cuenta que para todo esto, para mantener la compatibilidad cruzada de bash / zsh, arreglaremos ambos casos
typeset
y,declare
por lo tanto, el código debería funcionar en uno o ambos shells. Esto agrega algo de volumen y desorden que podrían eliminarse si solo estuvieras haciendo esto para un shell u otro.El principal problema con el uso de funciones para esto (o incluir el código en otras funciones) es que la
typeset
función genera código que, cuando se origina 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 hacks. Mi intento inicial de solucionar esto fue analizar la salida del proceso de serialización
sed
para agregar el-g
indicador para que el código creado defina una variable global cuando se obtiene de nuevo.Tenga en cuenta que la
sed
expresión funky es solo coincidir con la primera aparición de 'typeset' o 'declare' y agregar-g
como primer argumento. Es necesario que solo coincida con la primera aparición porque, como Stéphane Chazelas señaló correctamente en los comentarios, de lo contrario también coincidirá con los casos en que la cadena serializada contenga nuevas líneas literales seguidas de la palabra declarar o componer.Además de corregir mis análisis sintáctico iniciales paso en falso , Stéphane también sugirió una manera menos frágil que cortar esto que no sólo los pasos laterales de los problemas con el análisis de las cuerdas, pero podría ser un gancho útil añadir funcionalidad adicional mediante el uso de una función de contenedor para redefinir las acciones tomado al obtener los datos nuevamente. Esto supone que no está jugando ningún otro juego con los comandos declarar o componer, pero esta técnica sería más fácil de implementar en una situación en la que estuviera incluyendo esta funcionalidad como parte de otra función propia o usted no tenía el control de los datos que se escribían y si tenía o no
-g
agregado el indicador. Algo similar también se podría hacer con alias, vea la 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:
Con cualquiera de las soluciones, el uso se vería así:
fuente
declare
es elbash
equivalente deksh
'stypeset
.bash
,zsh
también soportetypeset
por lo que en ese sentido,typeset
es más portátil.export -p
es POSIX, pero no toma ningún argumento y su salida depende del shell (aunque está bien especificado para shells POSIX, por ejemplo, cuando se llama a bash o ksh comosh
). Recuerde citar sus variables; usar el operador split + glob aquí no tiene sentido.-E
solo se encuentra en algunos BSDsed
. Los valores variables pueden contener caracteres de nueva línea, por losed 's/^.../.../'
que no se garantiza que funcione correctamente.a=$'foo\ndeclare bar' bash -c 'declare -p a'
para instalar generará una línea que comienza condeclare
. Probablemente sea mejor hacerlodeclare() { builtin declare -g "$@"; }
antes de llamarsource
(y desarmar después)shopt -s expandalias
cuando no es interactivo. Con las funciones, también puede mejorar eldeclare
contenedor para que solo restaure las variables que especifique.Utilice la redirección, la sustitución de comandos y la expansión de parámetros. Las comillas dobles son necesarias para preservar espacios en blanco y caracteres especiales. El final
x
guarda las nuevas líneas finales que de otro modo se eliminarían en la sustitución del comando.fuente
Serializar todo: POSIX
En cualquier shell POSIX, puede serializar todas las variables de entorno con
export -p
. Esto no incluye variables de shell no exportadas. La salida se cita correctamente para que pueda volver a leerla en el mismo shell y obtener exactamente los mismos valores variables. Es posible que la salida no sea legible en otro shell, por ejemplo, ksh usa la$'…'
sintaxis no POSIX .Serializar algunos o todos: ksh, bash, zsh
Ksh (pdksh / mksh y ATT ksh), bash y zsh proporcionan una mejor instalación con el
typeset
incorporado.typeset -p
imprime todas las variables definidas y sus valores (zsh omite los valores de las variables que se han ocultado contypeset -H
). La salida contiene una declaración adecuada para que las variables de entorno se exporten cuando se vuelven a leer (pero si una variable ya se exporta cuando se vuelve a leer, no se exportará), de modo que las matrices se vuelven a leer como matrices, etc. Aquí también, la salida se cita correctamente pero solo se garantiza que se pueda leer 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.En bash y zsh, la restauración no se puede hacer desde una función porque las
typeset
declaraciones dentro de una función tienen el alcance de esa función. Debe ejecutar. ./some_vars
en el contexto en el que desea utilizar los valores de las variables, teniendo cuidado de que las variables que fueron globales cuando se exportaron se volverán 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:En bash (que usa en
declare
lugar detypeset
):En ksh,
typeset
declara variables locales en funciones definidas confunction function_name { … }
y variables globales en funciones definidas confunction_name () { … }
.Serializar algunos - POSIX
Si desea más control, puede exportar el contenido de una variable manualmente. Para imprimir el contenido de una variable exactamente en un archivo, use el
printf
incorporado (echo
tiene algunos casos especiales, comoecho -n
en algunos shells y agrega una nueva línea):Puede volver a leer esto con
$(cat VAR.content)
, excepto que la sustitución del comando elimina las nuevas líneas finales. Para evitar esta arruga, organice que la salida no termine nunca con una nueva línea.Si desea imprimir varias variables, puede citarlas con comillas simples y reemplazar todas las comillas simples incrustadas
'\''
. Esta forma de cita se puede volver a leer en cualquier shell de 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 volverán a leer como cadenas), no trata de tratar con variables de matriz en shells que las tienen.Aquí hay otro enfoque que no bifurca un subproceso pero es más pesado en la manipulación de cadenas.
Tenga en cuenta que en los shells que permiten variables de solo lectura, obtendrá un error si intenta volver a leer una variable que es de solo lectura.
fuente
$PWD
y$_
- por favor vea sus propios comentarios a continuación.typeset
un aliastypeset -g
?Muchas gracias a @ stéphane-chazelas que señaló todos los problemas con mis intentos anteriores, ahora parece funcionar para serializar una matriz en stdout o en una variable.
Esta técnica no analiza la entrada de shell (a diferencia de
declare -a
/declare -p
) y, por lo tanto, es segura contra la inserción maliciosa de metacaracteres en el texto serializado.Nota: las nuevas líneas no se escapan, ya que
read
elimina el\<newlines>
par de caracteres, por lo que-d ...
debe pasarse a leer, y luego se conservan las nuevas líneas sin escapes.Todo esto se gestiona en la
unserialise
función.Se utilizan dos caracteres mágicos, el separador de campo y el separador de registros (para que se puedan serializar múltiples matrices en la misma secuencia).
Estos caracteres se pueden definir como
FS
y,RS
pero tampoco se pueden definir comonewline
caracteres porque se elimina una nueva línea escapadaread
.El carácter de escape debe ser
\
la barra diagonal inversa, ya que eso es lo que se utilizaread
para evitar que el personaje sea reconocido como unIFS
personaje.serialise
se serializará"$@"
en stdout,serialise_to
se serializará en la variable nombrada en$1
y no serializar con:
o
p.ej
(sin una nueva línea al final)
léelo de nuevo:
o
Bash
read
respeta el carácter de escape\
(a menos que pase el indicador -r) para eliminar el significado especial de los caracteres, como la separación del campo de entrada o la delimitación de línea.Si desea serializar una matriz en lugar de una simple lista de argumentos, simplemente pase su matriz como la lista de argumentos:
Puede usarlo
unserialise
en un bucle como lo haríaread
porque es solo una lectura ajustada, pero recuerde que la secuencia no está separada por una nueva línea:fuente
bash
y loszsh
representa como$'\xxx'
. Prueba conbash -c $'printf "%q\n" "\t"'
obash -c $'printf "%q\n" "\u0378"'
$IFS
que no se modifique y ahora no puede restaurar los elementos de matriz vacíos correctamente. De hecho, tendría más sentido usar un valor diferente de IFS, y usarlo-d ''
para evitar tener que escapar de la nueva línea. Por ejemplo, use:
como separador de campo y solo escape de eso y de la barra diagonal inversa y useIFS=: read -ad '' array
para importar.read
. barra invertida-nueva línea pararead
es una forma de continuar una línea lógica en otra línea física. Editar: ah, veo que ya mencionas el problema con la nueva línea.Podrías usar
base64
:fuente
Otra forma de hacerlo es asegurarte de manejar todas las
'
citas físicas como esta:O con
export
:Las opciones primera y segunda funcionan en cualquier shell POSIX, suponiendo que el valor de la variable no contenga la cadena:
La tercera opción debería funcionar para cualquier shell POSIX, pero puede intentar definir otras variables como
_
oPWD
. Sin embargo, la verdad es que las únicas variables que podría intentar definir son establecidas y mantenidas por el propio shell, por lo que incluso si importaexport
el valor de cualquiera de ellas,$PWD
por ejemplo, el shell simplemente las restablecerá a el valor correcto inmediatamente de todos modos, intente hacerloPWD=any_value
y vea por usted mismo.Y debido a que, al menos con GNU, la
bash
salida de depuración se cita automáticamente de forma segura para volver a ingresar al shell, esto funciona independientemente de la cantidad de'
comillas en"$VAR"
:$VAR
luego se puede establecer en el valor guardado en cualquier script en el que la siguiente ruta sea válida con:fuente
$$
es el PID del shell en ejecución, ¿entendiste mal y mal\$
? El enfoque básico de usar un documento aquí podría funcionar, pero es complicado, no es un material de una sola línea: cualquiera que elija como marcador final, debe elegir algo que no aparezca en la cadena.$VAR
contiene%
. El tercer comando no siempre funciona con valores que contienen varias líneas (incluso después de agregar las comillas dobles obviamente faltantes).env
. Todavía tengo curiosidad por lo que quieres decir con las múltiples líneas:sed
elimina cada línea hasta que se encuentranVAR=
hasta la última, por lo que todas las líneas$VAR
se pasan. ¿Puedes dar un ejemplo que lo rompa?VAR
) no se modificaPWD
o_
o tal vez otros que definen algunas conchas. El segundo método requiere bash; el formato de salida de-v
no está estandarizado (ninguno de dash, ksh93, mksh y zsh funcionan).Casi igual pero un poco diferente:
De tu guión:
Este tiempo anterior está probado.
fuente
'
,*
, etc.echo "$LVALUE=\"$RVALUE\""
se supone que también debe mantener las nuevas líneas y el resultado en el archivo cfg_file debería ser como: MY_VAR1 = "Line1 \ nLine 2" Así, cuando eval MY_VAR1 también contendrá las nuevas líneas. Por supuesto, puede tener problemas si su valor almacenado contiene"
char. Pero eso también podría solucionarse.