Yes, in both bash and zsh you can serialize the contents of a variable in a way that is easy to retrieve using the declare builtin and the -p argument. The output format is such that you can simply source the output to get your stuff back.
# You have $VAR already with your stuff
declare -p VAR > serialized_VAR.sh
Either later in your script or in another script altogether, you can get your stuff back like this:
# Load up the serialized data back into $VAR
source serialized_VAR.sh
How portable this is beyond bash and zsh I don't know. It works in sh for me in testing but I don't know what the POSIX standard is on this.
You could generalize this a little bit and make pair of functions to save stuff to a folder of serialized data and read it back on demand. Obviously you would need to be aware that the stuff in the serialized directory will get executed as shell code so securing it is paramount to your script's security!
# Did you see the warning? Don't do this if you can't trust the files
# ./serialized_* as they will be executed as part of your script!
serialize() {
declare -p "$1" | sed -E 's/^(typeset|declare)/& -g/' > "./serialized_$1.sh"
}
deserialize() {
source "./serialized_$1.sh"
}
Note the dirty hack with sed is to add the -g flag to the declare statement. Without this, declare would create a local variable when run again from inside the other function. Matching 'typeset' as well as 'declare' is for portability between bash and zsh; you could clean that up if you were only running this with one or the other.
Stéphane Chazelas suggested a cleaner way to hack this in comments that avoids a potential problem case if the variable data you are serializing contains literal newlines. This assumes you are not playing any other games with the declare or typeset commands. Implementing this would look something like this:
serialize() {
declare -p "$1" > ./serialized_$1.sh
}
deserialize() {
declare() { builtin declare -g "$@"; }
typeset() { builtin typeset -g "$@"; }
source "./serialized_$1.sh"
unset -f declare typeset
}
With either solution, usage would look like this:
FOO=(an array or something)
BAR=$(uptime)
serialize FOO
serialize BAR
unset FOO BAR
# <snip> later....
deserialize FOO
deserialize BAR
echo "FOO: $FOO\nBAR: $BAR"
If keeping the values in a single file is a must, you could do something like so:
touch ./serializedvars.sh
serialize() {
sed -iE "/^($1=|(typeset|declare)[^=]*$1)/d" ./serializedvars.sh
declare -p "$1" | sed -E 's/^(typeset|declare)/& -g/' >> ./serializedvars.sh
}
deserialize() {
source <(sed -nE "/^($1=|(typeset|declare)[^=]*$1)/p" ./serializedvars.sh)
}
Warning: That implementation will break rather badly for variables that contain literal newlines. In order to get around that problem, the easiest thing to do is to use per-variable files for the serialized data per the example above. The other way would be to always append the data without bothering to delete any previous instances of a variable name from the data file. Only the last instance will "stick" anyway, but this could easily get to be unwieldy as your data file grows.
Again, some of the complexity of the expressions here are to accommodate both bash and zsh output formats from declare -p. One or the other would be notably simpler.