123

In my script in bash, there are lot of variables, and I have to make something to save them to file. My question is how to list all variables declared in my script and get list like this:

VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi
codeforester
  • 39,467
  • 16
  • 112
  • 140
lauriys
  • 4,652
  • 7
  • 32
  • 40
  • I believe `typeset -m ` was made for this: `noglob typeset -m VARIABLE*` will give you exactly what you wrote in your question's code block. (Answer:https://stackoverflow.com/a/73326324/13992057) – 8c6b5df0d16ade6c Aug 11 '22 at 20:05

15 Answers15

182

set will output the variables, unfortunately it will also output the functions defines as well.

Luckily POSIX mode only outputs the variables:

( set -o posix ; set ) | less

Piping to less, or redirect to where you want the options.

So to get the variables declared in just the script:

( set -o posix ; set ) >/tmp/variables.before
source script
( set -o posix ; set ) >/tmp/variables.after
diff /tmp/variables.before /tmp/variables.after
rm /tmp/variables.before /tmp/variables.after

(Or at least something based on that :-) )

Douglas Leeder
  • 52,368
  • 9
  • 94
  • 137
  • You edited while I was posting. Nice call with the `-o posix` now a diff will only contain the variables. – ezpz Aug 20 '09 at 10:50
  • 9
    Without using temporary files: `VARS="\`set -o posix ; set\`"; source script; SCRIPT_VARS="\`grep -vFe "$VARS" <<<"$(set -o posix ; set)" | grep -v ^VARS=\`"; unset VARS;` . This will also output the vars in a ready-to-save format. The list will include the variables that the script changed (it depends whether this is desirable) – ivan_pozdeev Aug 15 '11 at 22:12
  • unfortunately posix mode also outputs vars that are unsettable, so you can't "source" the output of it and read them all back in. – Erik Aronesty Dec 26 '13 at 21:20
  • 3
    @ErikAronesty If you want to be able to recreate an environment with `source`, you should be able to do so with the output of `declare -p`. – Six Sep 13 '15 at 13:47
  • 3
    If you want to compare an environment before and after with diff (or any similar command) without resorting to the use of temp files, you can do so with the following: `before=$(set -o posix; set); dosomestuff; diff <(echo "$before") <(set -o posix; set)` – Six Sep 13 '15 at 13:51
  • 1
    As a note, the output of `set` is not really grep-friendly. If you set a multi-line variable, `set` outputs the linebreaks verbosely. Then, if one of the lines looks like `FOO=...`, it looks just like a variable… – Michał Górny Nov 16 '15 at 19:34
  • Sadly, this does not seem to list arrays declared with `declare -a` (or `-A`). – Caesar Jun 04 '18 at 02:14
  • Cool solution! As I wanted this interactively only when testing around, this simpler approach, based on your solution, was sufficient to me: `( set -o posix ; set ) | tail -4` (this returns the last 4 variables, modify this number to fit your needs) – Nicolas Jul 21 '18 at 11:26
  • @Caesar Arrays declared with `declare -a/-A` are printed in my GNU bash 4.4.19. – Socowi Mar 05 '19 at 22:11
  • @Socowi Did you try declaring them but leaving them empty? `declare -a testa; set -o posix; set | grep testa` does not display anything with my bash 5.0. – Caesar Mar 12 '19 at 04:54
  • 2
    @Caesar No, I only tried non-empty arrays. Your are right in case of empty arrays – they are not printed. – Socowi Mar 12 '19 at 07:47
  • 2
    For any fellow zsh user: the posix option does not work in zsh (`set: no such option: posix`) - we have to execute the command in /bin/bash instead. – darthn Oct 17 '20 at 10:27
  • Since you're doing "before" and "after" with `diff`, then you don't need to resort to the `-o posix` option. Besides, with that option, variables containing non-printable characters will not be printed nicely. – Pourko Mar 07 '21 at 01:04
  • @darthn in zsh, you don't have to set posix; just `set` works. – Marcus Müller Oct 31 '21 at 17:39
  • The suggestions above to use a variable to hold the output of the first `set` (https://stackoverflow.com/questions/1305237/how-to-list-variables-declared-in-script-in-bash#comment8461169_1305273 and https://stackoverflow.com/questions/1305237/how-to-list-variables-declared-in-script-in-bash#comment52956691_1305273) to avoid a temp file will fail when the file being sourced includes a declaration of that same variable. – Ed Morton Jun 06 '22 at 12:54
50
compgen -v

It lists all variables including local ones. I learned it from Get list of variables whose name matches a certain pattern, and used it in my script.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Juven Xu
  • 629
  • 5
  • 5
15
for i in _ {a..z} {A..Z}; do eval "echo \${!$i@}" ; done | xargs printf "%s\n"

This must print all shell variables names. You can get a list before and after sourcing your file just like with "set" to diff which variables are new (as explained in the other answers). But keep in mind such filtering with diff can filter out some variables that you need but were present before sourcing your file.

In your case, if you know your variables' names start with "VARIABLE", then you can source your script and do:

for var in ${!VARIABLE@}; do
   printf "%s%q\n" "$var=" "${!var}"
done

UPDATE: For pure BASH solution (no external commands used):

for i in _ {a..z} {A..Z}; do
   for var in `eval echo "\\${!$i@}"`; do
      echo $var
      # you can test if $var matches some criteria and put it in the file or ignore
   done 
done
akostadinov
  • 17,364
  • 6
  • 77
  • 85
  • 3
    +1 and a oneliner version (with a single eval): `eval "printf '%q\n' $(printf ' "${!%s@}"' _ {a..z} {A..Z})"` – netj Nov 21 '19 at 23:28
  • 2
    Whoa! What is this black magic `"\\${!$i@}"`?! I see that something simple like `echo "${!BASH_C@}"` dumps all variables starting with `BASH_C`, but why? `!` is the indirect operator, but indirect over what? We end up listing the variables, not their values. What is going on! :) – Christopher King Nov 13 '20 at 21:57
  • 3
    Ah, I should have just read to the end of https://tldp.org/LDP/abs/html/parameter-substitution.html because that's where its described.. – Christopher King Nov 13 '20 at 21:58
6

Based on some of the above answers, this worked for me:

before=$(set -o posix; set | sort);

source file:

comm -13 <(printf %s "$before") <(set -o posix; set | sort | uniq) 
Cesar Roque
  • 61
  • 1
  • 3
5

If you can post-process, (as already mentioned) you might just place a set call at the beginning and end of your script (each to a different file) and do a diff on the two files. Realize that this will still contain some noise.

You can also do this programatically. To limit the output to just your current scope, you would have to implement a wrapper to variable creation. For example

store() {
    export ${1}="${*:2}"
    [[ ${STORED} =~ "(^| )${1}($| )" ]] || STORED="${STORED} ${1}"
}

store VAR1 abc
store VAR2 bcd
store VAR3 cde

for i in ${STORED}; do
    echo "${i}=${!i}"
done

Which yields

VAR1=abc
VAR2=bcd
VAR3=cde
ezpz
  • 11,767
  • 6
  • 38
  • 39
4

A little late to the party, but here's another suggestion:

#!/bin/bash

set_before=$( set -o posix; set | sed -e '/^_=*/d' )

# create/set some variables
VARIABLE1=a
VARIABLE2=b
VARIABLE3=c

set_after=$( set -o posix; unset set_before; set | sed -e '/^_=/d' )
diff  <(echo "$set_before") <(echo "$set_after") | sed -e 's/^> //' -e '/^[[:digit:]].*/d'

The diff+sed pipeline command line outputs all script-defined variables in the desired format (as specified in the OP's post):

VARIABLE1=a
VARIABLE2=b
VARIABLE3=c
Jim Fischer
  • 405
  • 4
  • 13
3

Here's something similar to the @GinkgoFr answer, but without the problems identified by @Tino or @DejayClayton, and is more robust than @DouglasLeeder's clever set -o posix bit:

+ function SOLUTION() { (set +o posix; set) | sed -ne '/^\w\+=/!q; p;'; }

The difference is that this solution STOPS after the first non-variable report, e.g. the first function reported by set

BTW: The "Tino" problem is solved. Even though POSIX is turned off and functions are reported by set, the sed ... portion of the solution only allows variable reports through (e.g. VAR=VALUE lines). In particular, the A2 does not spuriously make it into the output.

+ function a() { echo $'\nA2=B'; }; A0=000; A9=999; 
+ SOLUTION | grep '^A[0-9]='
A0=000
A9=999

AND: The "DejayClayton" problem is solved (embedded newlines in variable values do not disrupt the output - each VAR=VALUE get a single output line):

+ A1=$'111\nA2=222'; A0=000; A9=999; 
+ SOLUTION | grep '^A[0-9]='
A0=000
A1=$'111\nA2=222'
A9=999

NOTE: The solution provided by @DouglasLeeder suffers from the "DejayClayton" problem (values with embedded newlines). Below, the A1 is wrong and A2 should not show at all.

$ A1=$'111\nA2=222'; A0=000; A9=999; (set -o posix; set) | grep '^A[0-9]='
A0=000
A1='111
A2=222'
A9=999

FINALLY: I don't think the version of bash matters, but it might. I did my testing / developing on this one:

$ bash --version
GNU bash, version 4.4.12(1)-release (x86_64-pc-msys)

POST-SCRIPT: Given some of the other responses to the OP, I'm left < 100% sure that set always converts newlines within the value to \n, which this solution relies upon to avoid the "DejayClayton" problem. Perhaps that's a modern behavior? Or a compile-time variation? Or a set -o or shopt option setting? If you know of such variations, please add a comment...

Stevel
  • 631
  • 5
  • 13
2

If you're only concerned with printing a list of variables with static values (i.e. expansion doesn't work in this case) then another option would be to add start and end markers to your file that tell you where your block of static variable definitions is, e.g.

#!/bin/bash

# some code

# region variables
VAR1=FOO
VAR2=BAR
# endregion

# more code

Then you can just print that part of the file.

Here's something I whipped up for that:

function show_configuration() {
   local START_LINE=$(( $(< "$0" grep -m 1 -n "region variables" | cut -d: -f1) + 1 ))
   local END_LINE=$(( $(< "$0" grep -m 1 -n "endregion" | cut -d: -f1) - 1 ))
   < "$0" awk "${START_LINE} <= NR && NR <= ${END_LINE}"
}

First, note that the block of variables resides in the same file this function is in, so I can use $0 to access the contents of the file.

I use "region" markers to separate different regions of code. So I simply grep for the "variable" region marker (first match: grep -m 1) and let grep prefix the line number (grep -n). Then I have to cut the line number from the match output (splitting on :). Lastly, add or subtract 1 because I don't want the markers to be part of the output.

Now, to print that range of the file I use awk with line number conditions.

Max Leske
  • 5,007
  • 6
  • 42
  • 54
0

Try using a script (lets call it "ls_vars"):

  #!/bin/bash
  set -a
  env > /tmp/a
  source $1
  env > /tmp/b
  diff /tmp/{a,b} | sed -ne 's/^> //p'

chmod +x it, and:

  ls_vars your-script.sh > vars.files.save
Chen Levy
  • 15,438
  • 17
  • 74
  • 92
0

From a security perspective, either @akostadinov's answer or @JuvenXu's answer is preferable to relying upon the unstructured output of the set command, due to the following potential security flaw:

#!/bin/bash

function doLogic()
{
    local COMMAND="${1}"
    if ( set -o posix; set | grep -q '^PS1=' )
    then
        echo 'Script is interactive'
    else
        echo 'Script is NOT interactive'
    fi
}

doLogic 'hello'   # Script is NOT interactive
doLogic $'\nPS1=' # Script is interactive

The above function doLogic uses set to check for the presence of variable PS1 to determine if the script is interactive or not (never mind if this is the best way to accomplish that goal; this is just an example.)

However, the output of set is unstructured, which means that any variable that contains a newline can totally contaminate the results.

This, of course, is a potential security risk. Instead, use either Bash's support for indirect variable name expansion, or compgen -v.

Dejay Clayton
  • 3,710
  • 2
  • 29
  • 20
0

Try this : set | egrep "^\w+=" (with or without the | less piping)

The first proposed solution, ( set -o posix ; set ) | less, works but has a drawback: it transmits control codes to the terminal, so they are not displayed properly. So for example, if there is (likely) a IFS=$' \t\n' variable, we can see:

IFS='
'

…instead.

My egrep solution displays this (and eventually other similars ones) properly.

GingkoFr
  • 72
  • 5
  • it's been 8 years, you know – lauriys Mar 09 '17 at 15:22
  • **Downvoted because it is plain wrong** `bash -c $'a() { echo "\nA=B"; }; unset A; set | egrep "^\w+="' | grep ^A` displays `A=B"` -> Fail! – Tino Dec 30 '17 at 11:19
  • Strange test that seems to imply only the "_" (last argument to the previous command) pseudo-variable, but doesn't fail on others, especially IFS. Running it under "bash -c" could also making it not significant. You should at least have stayed neutral instead of downvoting. – GingkoFr Apr 05 '18 at 07:30
0

I probably have stolen the answer while ago ... anyway slightly different as a func:

    ##
    # usage source bin/nps-bash-util-funcs
    # doEchoVars
    doEchoVars(){

        # if the tmp dir does not exist
        test -z ${tmp_dir} && \
        export tmp_dir="$(cd "$(dirname $0)/../../.."; pwd)""/dat/log/.tmp.$$" && \
        mkdir -p "$tmp_dir" && \
        ( set -o posix ; set )| sort >"$tmp_dir/.vars.before"


        ( set -o posix ; set ) | sort >"$tmp_dir/.vars.after"
        cmd="$(comm -3 $tmp_dir/.vars.before $tmp_dir/.vars.after | perl -ne 's#\s+##g;print "\n $_ "' )"
        echo -e "$cmd"
    } 
Community
  • 1
  • 1
Yordan Georgiev
  • 5,114
  • 1
  • 56
  • 53
0

The printenv command:

printenv prints all environment variables along with their values.

Good Luck...

Aakash
  • 21,375
  • 7
  • 100
  • 81
  • OP wants script variables not environment variables. You would have to export the script variables if you wanted `printenv` to catch them. – shrewmouse Jul 16 '21 at 13:24
0

Simple way to do this is to use bash strict mode by setting system environment variables before running your script and to use diff to only sort the ones of your script :

# Add this line at the top of your script :
set > /tmp/old_vars.log

# Add this line at the end of your script :
set > /tmp/new_vars.log

# Alternatively you can remove unwanted variables with grep (e.g., passwords) :
set | grep -v "PASSWORD1=\|PASSWORD2=\|PASSWORD3=" > /tmp/new_vars.log

# Now you can compare to sort variables of your script :
diff /tmp/old_vars.log /tmp/new_vars.log | grep "^>" > /tmp/script_vars.log

You can now retrieve variables of your script in /tmp/script_vars.log. Or at least something based on that!

darkmaster
  • 44
  • 2
0

TL;DR

With: typeset -m <GLOBPATH>

$ VARIABLE1=abc
$ VARIABLE2=def
$ VARIABLE3=ghi
$ noglob typeset -m VARIABLE*
VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi

¹ documentation for typeset can be found in man zshbuiltins, or man zshall.

8c6b5df0d16ade6c
  • 1,910
  • 15
  • 15