Làm thế nào để liệt kê các biến được khai báo trong script trong bash?


98

Trong tập lệnh của tôi ở dạng bash, có rất nhiều biến và tôi phải tạo một cái gì đó để lưu chúng vào tệp. Câu hỏi của tôi là làm thế nào để liệt kê tất cả các biến được khai báo trong tập lệnh của tôi và nhận danh sách như sau:

VARIABLE1=abc
VARIABLE2=def
VARIABLE3=ghi

Câu trả lời:


151

set sẽ xuất ra các biến, tiếc là nó cũng sẽ xuất ra các hàm được định nghĩa.

May mắn thay, chế độ POSIX chỉ xuất ra các biến:

( set -o posix ; set ) | less

Đường ống đến less hoặc chuyển hướng đến nơi bạn muốn các tùy chọn.

Vì vậy, để lấy các biến được khai báo chỉ trong 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

(Hoặc ít nhất là một cái gì đó dựa trên :-))


Bạn đã chỉnh sửa trong khi tôi đang đăng. Cuộc gọi tuyệt vời với -o posixbây giờ một khác biệt sẽ chỉ chứa các biến.
ezpz

8
Mà không sử dụng tập tin tạm thời: VARS="`set -o posix ; set`"; source script; SCRIPT_VARS="`grep -vFe "$VARS" <<<"$(set -o posix ; set)" | grep -v ^VARS=`"; unset VARS;. Điều này cũng sẽ xuất ra các vars ở định dạng sẵn sàng để lưu. Danh sách này sẽ bao gồm các biến kịch bản thay đổi (nó phụ thuộc cho dù đây là mong muốn)
ivan_pozdeev

1
@ErikAronesty Nếu bạn muốn có thể tạo lại môi trường với source, bạn sẽ có thể làm như vậy với đầu ra của declare -p.
Sáu

2
Nếu bạn muốn so sánh một môi trường trước và sau với diff (hoặc bất kỳ lệnh nào tương tự) mà không cần sử dụng các tệp tạm thời, bạn có thể làm như vậy với những điều sau:before=$(set -o posix; set); dosomestuff; diff <(echo "$before") <(set -o posix; set)
Sáu

1
@Caesar Không, tôi chỉ thử các mảng không trống. Của bạn đúng trong trường hợp các mảng trống - chúng không được in.
Socowi


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

Điều này phải in tất cả các tên biến shell. Bạn có thể nhận được một danh sách trước và sau khi tìm nguồn cung cấp tệp của mình giống như với "set" để khác với các biến mới (như được giải thích trong các câu trả lời khác). Nhưng hãy nhớ rằng việc lọc như vậy với diff có thể lọc ra một số biến mà bạn cần nhưng đã có trước khi tìm nguồn cung cấp tệp của bạn.

Trong trường hợp của bạn, nếu bạn biết tên biến của mình bắt đầu bằng "VARIABLE", thì bạn có thể tạo nguồn tập lệnh của mình và thực hiện:

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

CẬP NHẬT: Đối với giải pháp BASH thuần túy (không sử dụng lệnh bên ngoài):

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 và phiên bản oneliner (với một đánh giá duy nhất):eval "printf '%q\n' $(printf ' "${!%s@}"' _ {a..z} {A..Z})"
netj

4

Dựa trên một số câu trả lời ở trên, điều này phù hợp với tôi:

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

tệp nguồn :

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

3

Nếu bạn có thể xử lý hậu kỳ, (như đã đề cập), bạn có thể chỉ cần thực hiện setlệnh gọi ở đầu và cuối tập lệnh của mình (mỗi tập lệnh với một tệp khác nhau) và thực hiện sự khác biệt trên hai tệp. Nhận ra rằng điều này sẽ vẫn chứa một số tiếng ồn.

Bạn cũng có thể làm điều này theo chương trình. Để giới hạn đầu ra chỉ trong phạm vi hiện tại của bạn, bạn sẽ phải triển khai trình bao bọc để tạo biến. Ví dụ

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

Kết quả nào

VAR1=abc
VAR2=bcd
VAR3=cde

2

Đây là một cái gì đó tương tự với câu trả lời @GinkgoFr, nhưng không có vấn đề được xác định bởi @Tino hoặc @DejayClayton và mạnh mẽ hơn set -o posixmột chút thông minh của @ DouglasLeeder :

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

Sự khác biệt là giải pháp này BƯỚC sau báo cáo không biến đầu tiên, ví dụ: hàm đầu tiên được báo cáo bởi set

BTW: Vấn đề "Tino" đã được giải quyết. Mặc dù POSIX bị tắt và các chức năng được báo cáo set, sed ...phần của giải pháp chỉ cho phép các báo cáo thay đổi thông qua (ví dụ: VAR=VALUEdòng). Đặc biệt, A2không không spuriously làm cho nó vào đầu ra.

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

VÀ: Vấn đề "DejayClayton" đã được giải quyết (các dòng mới được nhúng trong các giá trị biến không làm gián đoạn đầu ra - mỗi dòng VAR=VALUEnhận được một dòng đầu ra):

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

LƯU Ý: Giải pháp do @DouglasLeeder cung cấp gặp phải sự cố "DejayClayton" (các giá trị có dòng mới được nhúng). Dưới đây, A1là sai và A2không nên hiển thị ở tất cả.

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

CUỐI CÙNG: Tôi không nghĩ phiên bản của bashvấn đề, nhưng nó có thể xảy ra. Tôi đã thực hiện thử nghiệm / phát triển của mình trên cái này:

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

POST-SCRIPT: Với một số phản hồi khác cho OP, tôi chắc chắn <100% set luôn chuyển đổi các dòng mới trong giá trị thành \n, mà giải pháp này dựa vào để tránh sự cố "DejayClayton". Có lẽ đó là một hành vi hiện đại? Hay một biến thể thời gian biên dịch? Hoặc một set -ohoặc shoptthiết lập tùy chọn? Nếu bạn biết về các biến thể như vậy, vui lòng thêm một bình luận ...


0

Hãy thử sử dụng một tập lệnh (hãy gọi nó là "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, và:

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

0

Từ góc độ an ninh, hoặc @ của akostadinov câu trả lời hoặc @ JuvenXu của câu trả lời là thích hợp hơn để dựa vào đầu ra không có cấu trúc của setlệnh, do lỗ hổng bảo mật tiềm năng sau đây:

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

Hàm trên doLogicsử dụng setđể kiểm tra sự hiện diện của biến PS1để xác định xem script có tương tác hay không (đừng bận tâm nếu đây là cách tốt nhất để thực hiện mục tiêu đó; đây chỉ là một ví dụ).

Tuy nhiên, đầu ra của set là không có cấu trúc, có nghĩa là bất kỳ biến nào chứa một dòng mới đều có thể làm ô nhiễm hoàn toàn kết quả.

Tất nhiên, đây là một rủi ro bảo mật tiềm ẩn. Thay vào đó, hãy sử dụng sự hỗ trợ của Bash để mở rộng tên biến gián tiếp hoặc compgen -v.


0

Hãy thử điều này: set | egrep "^\w+="(có hoặc không có | lessđường ống)

Giải pháp được đề xuất đầu tiên ( set -o posix ; set ) | less, hoạt động nhưng có một nhược điểm: nó truyền các mã điều khiển đến thiết bị đầu cuối, vì vậy chúng không được hiển thị đúng cách. Vì vậy, ví dụ, nếu có (khả năng xảy ra) một IFS=$' \t\n'biến, chúng ta có thể thấy:

IFS='
'

…thay thế.

egrepGiải pháp của tôi hiển thị điều này (và cuối cùng là các giải pháp tương tự khác) đúng cách.


đã 8 năm rồi, bạn biết đấy
lauriys

Bị phản đối vì nó bash -c $'a() { echo "\nA=B"; }; unset A; set | egrep "^\w+="' | grep ^A hiển thị sai đơn giảnA=B" -> Không thành công!
Tino

Thử nghiệm kỳ lạ dường như chỉ ngụ ý biến giả "_" (đối số cuối cùng của lệnh trước đó), nhưng không thất bại với những người khác, đặc biệt là IFS. Chạy nó dưới "bash -c" cũng có thể làm cho nó không đáng kể. Ít nhất bạn nên giữ thái độ trung lập thay vì phản đối.
GingkoFr

0

Tôi có thể đã đánh cắp câu trả lời trong khi trước đây ... dù sao thì hơi khác với tư cách là một 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

Các printenvlệnh:

printenv in tất cả environment variables cùng với các giá trị của chúng.

Chúc may mắn...


0

Cách đơn giản để thực hiện việc này là sử dụng chế độ nghiêm ngặt bash bằng cách đặt các biến môi trường hệ thống trước khi chạy tập lệnh của bạn và sử dụng khác biệt để chỉ sắp xếp các biến trong tập lệnh của bạn:

# 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

Bây giờ bạn có thể truy xuất các biến của tập lệnh của mình trong /tmp/script_vars.log. Hoặc ít nhất là một cái gì đó dựa trên đó!


0

Đến bữa tiệc hơi muộn, nhưng đây là một gợi ý khác:

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

Dòng lệnh đường ống diff + sed xuất ra tất cả các biến do tập lệnh xác định ở định dạng mong muốn (như được chỉ định trong bài đăng của OP):

VARIABLE1=a
VARIABLE2=b
VARIABLE3=c
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.