จะแสดงรายการตัวแปรที่ประกาศในสคริปต์ใน bash ได้อย่างไร?


99

ในสคริปต์ของฉันใน bash มีตัวแปรมากมายและฉันต้องทำอะไรบางอย่างเพื่อบันทึกลงในไฟล์ คำถามของฉันคือวิธีแสดงรายการตัวแปรทั้งหมดที่ประกาศในสคริปต์ของฉันและรับรายการดังนี้:

VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi

คำตอบ:


151

set จะส่งออกตัวแปร แต่น่าเสียดายที่มันจะส่งออกฟังก์ชันที่กำหนดเช่นกัน

โชคดีที่โหมด POSIX แสดงเฉพาะตัวแปร:

( set -o posix ; set ) | less

ไปป์lessหรือเปลี่ยนเส้นทางไปยังตำแหน่งที่คุณต้องการตัวเลือก

ดังนั้นเพื่อให้ได้ตัวแปรที่ประกาศในสคริปต์:

( 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

(หรืออย่างน้อยก็ขึ้นอยู่กับสิ่งนั้น :-))


คุณแก้ไขในขณะที่ฉันโพสต์ การโทรที่ดีกับ-o posixตอนนี้ความแตกต่างจะมีเฉพาะตัวแปรเท่านั้น
ezpz

8
โดยไม่ต้องใช้ไฟล์ชั่วคราว: VARS="`set -o posix ; set`"; source script; SCRIPT_VARS="`grep -vFe "$VARS" <<<"$(set -o posix ; set)" | grep -v ^VARS=`"; unset VARS;. นอกจากนี้ยังจะแสดงผล vars ในรูปแบบพร้อมที่จะบันทึก รายการจะรวมตัวแปรที่สคริปต์เปลี่ยนแปลง (ขึ้นอยู่กับว่าสิ่งนี้เป็นที่ต้องการหรือไม่)
ivan_pozdeev

2
@ErikAronesty หากคุณต้องการสร้างสภาพแวดล้อมขึ้นมาใหม่sourceคุณควรจะทำได้ด้วยผลลัพธ์ของdeclare -pไฟล์.
หก

2
หากคุณต้องการเปรียบเทียบสภาพแวดล้อมก่อนและหลังด้วย diff (หรือคำสั่งที่คล้ายกัน) โดยไม่ต้องใช้ไฟล์ temp คุณสามารถทำได้ดังต่อไปนี้:before=$(set -o posix; set); dosomestuff; diff <(echo "$before") <(set -o posix; set)
6

1
@ Caesar ไม่ฉันลองเฉพาะอาร์เรย์ที่ไม่ว่างเปล่า ของคุณถูกต้องในกรณีที่อาร์เรย์ว่างเปล่า - ไม่มีการพิมพ์
Socowi

41
compgen -v

มันแสดงรายการตัวแปรทั้งหมดรวมถึงตัวแปรท้องถิ่น ผมได้เรียนรู้ได้จากรายชื่อรับของตัวแปรที่มีชื่อตรงกับรูปแบบบางอย่างและใช้มันในสคริปต์ของฉัน


2
น่าเสียดายที่compgen -vรายการตัวแปรส่วนกลางที่ไม่ได้ตั้งค่าในเครื่อง ไม่แน่ใจว่าเป็นจุดบกพร่องที่มีมานานหรือเป็นพฤติกรรมที่ต้องการ
MichałGórny

11
for i in _ {a..z} {A..Z}; do eval "echo \${!$i@}" ; done | xargs printf "%s\n"

ต้องพิมพ์ชื่อตัวแปรเชลล์ทั้งหมด คุณสามารถรับรายการก่อนและหลังการจัดหาไฟล์ของคุณได้เช่นเดียวกับการ "set" เพื่อให้ตัวแปรใดใหม่ (ตามที่อธิบายไว้ในคำตอบอื่น ๆ ) แต่โปรดทราบว่าการกรองด้วย diff ดังกล่าวสามารถกรองตัวแปรบางตัวที่คุณต้องการ แต่มีอยู่ก่อนที่จะจัดหาไฟล์ของคุณ

ในกรณีของคุณหากคุณทราบว่าชื่อตัวแปรของคุณขึ้นต้นด้วย "VARIABLE" คุณสามารถซอร์สสคริปต์ของคุณและทำ:

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

อัปเดต:สำหรับโซลูชัน BASH บริสุทธิ์ (ไม่ใช้คำสั่งภายนอก):

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

1
+1 และเวอร์ชัน oneliner (ที่มีการประเมินเดียว):eval "printf '%q\n' $(printf ' "${!%s@}"' _ {a..z} {A..Z})"
netj

4

จากคำตอบข้างต้นสิ่งนี้ใช้ได้ผลสำหรับฉัน:

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

ไฟล์ต้นฉบับ :

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

3

หากคุณสามารถโพสต์โพรเซสได้ (ดังที่ได้กล่าวไปแล้ว) คุณอาจจะsetโทรที่จุดเริ่มต้นและจุดสิ้นสุดของสคริปต์ของคุณได้ โปรดทราบว่าสิ่งนี้จะยังคงมีเสียงรบกวนอยู่

คุณยังสามารถดำเนินการตามโปรแกรมได้อีกด้วย หากต้องการ จำกัด เอาต์พุตให้เหลือเพียงขอบเขตปัจจุบันของคุณคุณจะต้องใช้ wrapper ในการสร้างตัวแปร ตัวอย่างเช่น

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

ซึ่งให้ผลตอบแทน

VAR1=abc
VAR2=bcd
VAR3=cde

2

นี่คือสิ่งที่คล้ายกับคำตอบของ @GinkgoFr แต่ไม่มีปัญหาที่ระบุโดย @Tino หรือ @DejayClayton และมีประสิทธิภาพมากกว่าset -o posixบิตที่ชาญฉลาดของ @ DouglasLeeder :

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

ความแตกต่างคือโซลูชันนี้จะหยุดหลังจากรายงานที่ไม่ใช่ตัวแปรแรกเช่นฟังก์ชันแรกที่รายงานโดย set

BTW: ปัญหา "Tino" ได้รับการแก้ไขแล้ว แม้ว่า POSIX จะปิดอยู่และมีการรายงานฟังก์ชันsetแต่sed ...ส่วนของโซลูชันจะอนุญาตให้รายงานตัวแปรผ่าน (เช่นVAR=VALUEบรรทัด) เท่านั้น โดยเฉพาะอย่างยิ่งA2ไม่ได้ spuriously ทำให้มันกลายเป็นเอาท์พุท

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

และ: ปัญหา "DejayClayton" ได้รับการแก้ไขแล้ว (บรรทัดใหม่ที่ฝังอยู่ในค่าตัวแปรจะไม่รบกวนผลลัพธ์ - แต่ละรายการVAR=VALUEจะได้รับบรรทัดผลลัพธ์เดียว)

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

หมายเหตุ: วิธีแก้ปัญหาโดย @DouglasLeeder ประสบปัญหา "DejayClayton" (ค่าที่มีเส้นขึ้นบรรทัดใหม่ฝังอยู่) ด้านล่างนี้A1ผิดและA2ไม่ควรแสดงเลย

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

ในที่สุด: ฉันไม่คิดว่าเวอร์ชั่นมีความbashสำคัญ แต่มันอาจจะ ฉันทำการทดสอบ / พัฒนาเกี่ยวกับสิ่งนี้:

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

POST-SCRIPT: จากคำตอบอื่น ๆ ที่มีต่อ OP ฉันมั่นใจว่า <100% set จะแปลงบรรทัดใหม่ภายในค่า\nเสมอซึ่งโซลูชันนี้อาศัยเพื่อหลีกเลี่ยงปัญหา "DejayClayton" บางทีนั่นอาจเป็นพฤติกรรมสมัยใหม่? หรือรูปแบบเวลาคอมไพล์? หรือการตั้งค่าset -oหรือshoptตัวเลือก? หากคุณรู้จักรูปแบบดังกล่าวโปรดเพิ่มความคิดเห็น ...


0

ลองใช้สคริปต์ (เรียกว่า "ls_vars"):

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

chmod + x มันและ:

  ls_vars your-script.sh > vars.files.save

0

จากมุมมองของการรักษาความปลอดภัยทั้ง @ akostadinov ของคำตอบหรือ @ JuvenXu ของคำตอบที่ดีกว่าที่จะอาศัยการส่งออกที่ไม่มีโครงสร้างของsetคำสั่งเนื่องจากข้อบกพร่องที่อาจเกิดขึ้นต่อไปนี้การรักษาความปลอดภัย:

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

ฟังก์ชันข้างต้นdoLogicใช้setตรวจสอบการมีอยู่ของตัวแปรPS1เพื่อตรวจสอบว่าสคริปต์เป็นแบบโต้ตอบหรือไม่ (ไม่ต้องกังวลว่านี่เป็นวิธีที่ดีที่สุดในการบรรลุเป้าหมายหรือไม่นี่เป็นเพียงตัวอย่างเท่านั้น)

อย่างไรก็ตามผลลัพธ์ของsetไม่มีโครงสร้างซึ่งหมายความว่าตัวแปรใด ๆ ที่มีการขึ้นบรรทัดใหม่สามารถปนเปื้อนผลลัพธ์ได้ทั้งหมด

แน่นอนว่านี่เป็นความเสี่ยงด้านความปลอดภัยที่อาจเกิดขึ้น ให้ใช้การสนับสนุนของ Bash สำหรับการขยายชื่อตัวแปรทางอ้อมแทนหรือcompgen -v .


0

ลองสิ่งนี้: set | egrep "^\w+="(มีหรือไม่มีไฟล์| lessท่อ)

โซลูชันแรกที่เสนอใช้( set -o posix ; set ) | lessงานได้ แต่มีข้อเสียคือส่งรหัสควบคุมไปยังเทอร์มินัลดังนั้นจึงแสดงไม่ถูกต้อง ตัวอย่างเช่นหากมี(น่าจะเป็น)IFS=$' \t\n'ตัวแปรเราจะเห็น:

IFS='
'

…แทน.

egrepโซลูชันของฉันแสดงสิ่งนี้(และในที่สุดก็คล้าย ๆ กัน)อย่างถูกต้อง


ผ่านมา 8 ปีแล้ว
lauriys

ลดลงเนื่องจากเป็นการ bash -c $'a() { echo "\nA=B"; }; unset A; set | egrep "^\w+="' | grep ^Aแสดงที่ไม่ถูกต้องA=B" -> ล้มเหลว!
Tino

การทดสอบแปลก ๆ ที่ดูเหมือนจะบ่งบอกเป็นเพียงตัวแปรหลอก "_" (อาร์กิวเมนต์สุดท้ายของคำสั่งก่อนหน้า) แต่ไม่ได้ล้มเหลวกับตัวอื่นโดยเฉพาะ IFS การเรียกใช้ภายใต้ "bash -c" อาจทำให้ไม่สำคัญ อย่างน้อยคุณควรวางตัวเป็นกลางแทนการลงคะแนน
GingkoFr

0

ฉันอาจจะขโมยคำตอบไปเมื่อก่อน ... แตกต่างกันเล็กน้อยในฐานะ 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"
    } 


0

วิธีง่ายๆในการทำเช่นนี้คือใช้โหมด bash ที่เข้มงวดโดยตั้งค่าตัวแปรสภาพแวดล้อมระบบก่อนรันสคริปต์ของคุณและใช้ diff เพื่อเรียงลำดับสคริปต์ของคุณเท่านั้น:

# 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

ตอนนี้คุณสามารถดึงตัวแปรของสคริปต์ของคุณใน /tmp/script_vars.log หรืออย่างน้อยก็เป็นไปตามนั้น!


0

ไปงานปาร์ตี้สายเล็กน้อย แต่นี่คือคำแนะนำอื่น:

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

diff + sedบรรทัดคำสั่งท่อเอาท์พุทตัวแปรสคริปต์ที่กำหนดไว้ทั้งหมดในรูปแบบที่ต้องการ (ตามที่ระบุไว้ในโพสต์ของ OP):

VARIABLE1=a
VARIABLE2=b
VARIABLE3=c
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.