Bagaimana cara membuat daftar variabel yang dideklarasikan dalam skrip di bash?

98

Di skrip saya di bash, ada banyak variabel, dan saya harus membuat sesuatu untuk menyimpannya ke file. Pertanyaan saya adalah bagaimana mencantumkan semua variabel yang dideklarasikan dalam skrip saya dan mendapatkan daftar seperti ini:

VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi
lauriys
sumber

Jawaban:

151

set akan menampilkan variabel, sayangnya itu juga akan menampilkan fungsi yang didefinisikan juga.

Untungnya mode POSIX hanya menampilkan variabel:

( set -o posix ; set ) | less

Mengalihkan ke less, atau mengalihkan ke tempat Anda menginginkan opsi.

Jadi untuk mendapatkan variabel yang dideklarasikan hanya dalam skrip:

( 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

(Atau setidaknya sesuatu berdasarkan itu :-))

Douglas Leeder
sumber
Anda mengeditnya saat saya memposting. Panggilan bagus dengan -o posixsekarang diff hanya akan berisi variabel.
ezpz
8
Tanpa menggunakan file-file sementara: VARS="`set -o posix ; set`"; source script; SCRIPT_VARS="`grep -vFe "$VARS" <<<"$(set -o posix ; set)" | grep -v ^VARS=`"; unset VARS;. Ini juga akan menampilkan vars dalam format yang siap disimpan. Daftar akan menyertakan variabel yang diubah skrip (tergantung apakah ini diinginkan)
ivan_pozdeev
1
@ErikAronesty Jika Anda ingin dapat membuat ulang lingkungan dengan source, Anda harus dapat melakukannya dengan keluaran declare -p.
Enam
2
Jika Anda ingin membandingkan lingkungan sebelum dan sesudah dengan diff (atau perintah yang serupa) tanpa menggunakan file temp, Anda dapat melakukannya dengan yang berikut:before=$(set -o posix; set); dosomestuff; diff <(echo "$before") <(set -o posix; set)
Enam
1
@Caesar Tidak, saya hanya mencoba array yang tidak kosong. Anda benar dalam kasus array kosong - mereka tidak dicetak.
Socowi
40
compgen -v

Ini mencantumkan semua variabel termasuk yang lokal. Saya mempelajarinya dari Dapatkan daftar variabel yang namanya cocok dengan pola tertentu , dan menggunakannya dalam skrip saya .

Juven Xu
sumber
2
Sayangnya, compgen -vdaftar juga variabel global yang tidak disetel secara lokal. Tidak yakin apakah itu bug lama atau perilaku yang diinginkan.
Michał Górny
10
for i in _ {a..z} {A..Z}; do eval "echo \${!$i@}" ; done | xargs printf "%s\n"

Ini harus mencetak semua nama variabel shell. Anda bisa mendapatkan daftar sebelum dan sesudah mencari file Anda seperti "set" ke diff variabel mana yang baru (seperti yang dijelaskan di jawaban lain). Namun perlu diingat bahwa pemfilteran dengan diff dapat menyaring beberapa variabel yang Anda butuhkan tetapi ada sebelum mencari file Anda.

Dalam kasus Anda, jika Anda tahu nama variabel Anda diawali dengan "VARIABEL", Anda dapat mengambil skrip Anda dan melakukan:

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

UPDATE: Untuk solusi BASH murni (tidak ada perintah eksternal yang digunakan):

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
sumber
1
+1 dan versi oneliner (dengan satu eval):eval "printf '%q\n' $(printf ' "${!%s@}"' _ {a..z} {A..Z})"
netj
4

Berdasarkan beberapa jawaban di atas, ini berhasil untuk saya:

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

file sumber :

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

Jika Anda dapat melakukan pasca-proses, (seperti yang telah disebutkan) Anda dapat melakukan setpanggilan di awal dan akhir skrip Anda (masing-masing ke file yang berbeda) dan melakukan perbedaan pada kedua file. Sadarilah bahwa ini masih akan mengandung sedikit kebisingan.

Anda juga dapat melakukan ini secara terprogram. Untuk membatasi keluaran hanya ke lingkup Anda saat ini, Anda harus menerapkan pembungkus untuk pembuatan variabel. Sebagai contoh

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

Hasil yang mana

VAR1=abc
VAR2=bcd
VAR3=cde
ezpz
sumber
2

Berikut ini sesuatu yang mirip dengan jawaban @GinkgoFr, tetapi tanpa masalah yang diidentifikasi oleh @Tino atau @DejayClayton, dan lebih kuat daripada set -o posixbit pintar @ DouglasLeeder :

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

Perbedaannya adalah bahwa solusi ini BERHENTI setelah laporan non-variabel pertama, misalnya fungsi pertama yang dilaporkan oleh set

BTW: Masalah "Tino" sudah terpecahkan. Meskipun POSIX dimatikan dan fungsi dilaporkan oleh set, sed ...porsi solusi hanya memungkinkan laporan variabel melalui (misalnya VAR=VALUEbaris). Secara khusus, A2tidak tidak spuriously membuatnya menjadi output.

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

DAN: Masalah "DejayClayton" telah terpecahkan (baris baru yang disematkan dalam nilai variabel tidak mengganggu keluaran - masing-masing VAR=VALUEmendapatkan satu baris keluaran):

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

CATATAN: Solusi yang disediakan oleh @DouglasLeeder mengalami masalah "DejayClayton" (nilai dengan baris baru yang disematkan). Di bawah ini, A1salah dan A2seharusnya tidak ditampilkan sama sekali.

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

AKHIRNYA: Menurut saya versi bashmasalahnya, tapi mungkin saja. Saya melakukan pengujian / pengembangan saya yang satu ini:

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

POST-SCRIPT: Mengingat beberapa tanggapan lain untuk OP, saya <100% yakin bahwa set selalu mengubah baris baru dalam nilai \n, yang mana solusi ini bergantung untuk menghindari masalah "DejayClayton". Mungkin itu perilaku modern? Atau variasi waktu kompilasi? Atau pengaturan set -oatau shoptopsi? Jika Anda mengetahui variasi seperti itu, silakan tambahkan komentar ...

Stevel
sumber
0

Coba gunakan skrip (sebut saja "ls_vars"):

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

chmod + x itu, dan:

  ls_vars your-script.sh > vars.files.save
Chen Levy
sumber
0

Dari perspektif keamanan, baik @ akostadinov ini jawaban atau @ JuvenXu ini jawabannya adalah lebih baik untuk mengandalkan output terstruktur dari setperintah, karena lubang keamanan potensial berikut:

#!/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

Fungsi di atas doLogicdigunakan setuntuk memeriksa keberadaan variabel PS1untuk menentukan apakah skrip itu interaktif atau tidak (tidak masalah apakah ini cara terbaik untuk mencapai tujuan itu; ini hanya sebuah contoh.)

Namun, keluaran dari settidak terstruktur, yang berarti bahwa variabel apa pun yang berisi baris baru dapat mencemari hasil secara total.

Ini, tentu saja, merupakan risiko keamanan potensial. Sebagai gantinya, gunakan dukungan Bash untuk perluasan nama variabel tidak langsung, atau compgen -v.

Dejay Clayton
sumber
0

Coba ini: set | egrep "^\w+="(dengan atau tanpa | lesspemipaan)

Solusi yang diusulkan pertama ( set -o posix ; set ) | less,, berfungsi tetapi memiliki kekurangan: ia mentransmisikan kode kontrol ke terminal, sehingga tidak ditampilkan dengan benar. Jadi misalnya, jika ada (kemungkinan) suatu IFS=$' \t\n'variabel, kita dapat melihat:

IFS='
'

…sebagai gantinya.

egrepSolusi saya menampilkan ini (dan akhirnya yang serupa lainnya) dengan benar.

GingkoFr
sumber
Sudah 8 tahun, Anda tahu
lauriys
Diturunkan karena ini adalah bash -c $'a() { echo "\nA=B"; }; unset A; set | egrep "^\w+="' | grep ^A tampilan yang salahA=B" -> Gagal!
Tino
Uji aneh yang tampaknya hanya menyiratkan variabel pseudo "_" (argumen terakhir ke perintah sebelumnya), tetapi tidak gagal pada yang lain, terutama IFS. Menjalankannya di bawah "bash -c" juga bisa membuatnya tidak signifikan. Anda setidaknya harus tetap netral daripada downvoting.
GingkoFr
0

Saya mungkin telah mencuri jawabannya beberapa waktu yang lalu ... toh sedikit berbeda sebagai fungsi:

    ##
    # 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"
    } 
Yordan Georgiev
sumber
0

The printenvperintah:

printenvmencetak semua environment variablesnilai mereka.

Semoga berhasil...

Akash
sumber
0

Cara sederhana untuk melakukannya adalah dengan menggunakan mode ketat bash dengan menyetel variabel lingkungan sistem sebelum menjalankan skrip Anda dan menggunakan diff untuk hanya mengurutkan skrip Anda:

# 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

Sekarang Anda dapat mengambil variabel dari skrip Anda di /tmp/script_vars.log. Atau setidaknya sesuatu berdasarkan itu!

grm34
sumber
0

Sedikit terlambat ke pesta, tapi inilah saran lainnya:

#!/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'

Baris perintah pipa diff + sed mengeluarkan semua variabel yang ditentukan skrip dalam format yang diinginkan (seperti yang ditentukan dalam posting OP):

VARIABLE1=a
VARIABLE2=b
VARIABLE3=c
Jim Fischer
sumber