Cách nhận biết nếu một chuỗi không được xác định trong tập lệnh shell Bash


151

Nếu tôi muốn kiểm tra chuỗi null tôi sẽ làm

[ -z $mystr ]

Nhưng nếu tôi muốn kiểm tra xem biến đã được xác định chưa? Hoặc không có sự phân biệt trong kịch bản Bash?


27
hãy tập thói quen sử dụng [-z "$ mystr"] trái ngược với [-z $ mystr]
Charles Duffy

Làm thế nào để làm điều ngược lại? Ý tôi là khi chuỗi không rỗng
Mở đường

1
@flow: Thế còn[ -n "${VAR+x}"] && echo not null
Lekensteyn

1
@CharlesDuffy Tôi đã đọc điều này trước đây trong rất nhiều tài nguyên trực tuyến. Tại sao điều này được ưa thích?
ffledgling

6
@Ayos vì nếu bạn không có dấu ngoặc kép, nội dung của biến là phân tách chuỗi và toàn cầu; nếu đó là một chuỗi rỗng, nó trở thành [ -z ]thay vì [ -z "" ]; nếu nó có không gian, nó trở thành [ -z "my" "test" ]thay vì [ -z my test ]; và nếu có [ -z * ], thì nó *được thay thế bằng tên của các tệp trong thư mục của bạn.
Charles Duffy

Câu trả lời:


138

Tôi nghĩ câu trả lời bạn là sau khi được ngụ ý (nếu không nói) bởi Vinko 's câu trả lời , mặc dù nó không được nêu ra đơn giản. Để phân biệt xem VAR được đặt nhưng trống hay không được đặt, bạn có thể sử dụng:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Bạn có thể kết hợp hai bài kiểm tra trên dòng thứ hai thành một với:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Tuy nhiên, nếu bạn đọc tài liệu về Autoconf, bạn sẽ thấy rằng họ không khuyên bạn nên kết hợp các thuật ngữ với ' -a' và khuyên bạn nên sử dụng các thử nghiệm đơn giản riêng biệt kết hợp với &&. Tôi đã không gặp phải một hệ thống có vấn đề; điều đó không có nghĩa là chúng không tồn tại (nhưng có lẽ chúng cực kỳ hiếm ngày nay, ngay cả khi chúng không hiếm trong quá khứ xa xôi).

Bạn có thể tìm thấy các chi tiết của chúng và các mở rộng tham số shell liên quan khác , lệnh testhoặc các [biểu thức điều kiện trong hướng dẫn Bash.


Gần đây tôi đã được hỏi qua email về câu trả lời này với câu hỏi:

Bạn sử dụng hai bài kiểm tra, và tôi hiểu rõ cái thứ hai, nhưng không phải cái thứ nhất. Chính xác hơn là tôi không hiểu sự cần thiết phải mở rộng biến

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Điều này sẽ không hoàn thành như vậy?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Câu hỏi công bằng - câu trả lời là 'Không, sự thay thế đơn giản hơn của bạn không làm điều tương tự'.

Giả sử tôi viết điều này trước bài kiểm tra của bạn:

VAR=

Thử nghiệm của bạn sẽ cho biết "VAR hoàn toàn không được đặt", nhưng tôi sẽ nói (theo hàm ý vì nó không có gì) "VAR được đặt nhưng giá trị của nó có thể trống". Hãy thử kịch bản này:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

Đầu ra là:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

Trong cặp thử nghiệm thứ hai, biến được đặt, nhưng nó được đặt thành giá trị trống. Đây là sự khác biệt mà các ký hiệu ${VAR=value}${VAR:=value}ký hiệu tạo ra. Ditto cho ${VAR-value}${VAR:-value}, ${VAR+value}${VAR:+value}, và như vậy.


Như Gili chỉ ra trong câu trả lời của anh ta , nếu bạn chạy bashvới set -o nounsettùy chọn, thì câu trả lời cơ bản ở trên không thành công unbound variable. Nó dễ dàng được khắc phục:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Hoặc bạn có thể hủy set -o nounsettùy chọn với set +u( set -utương đương với set -o nounset).


7
Vấn đề duy nhất tôi có với câu trả lời này là nó hoàn thành nhiệm vụ theo cách khá gián tiếp và không rõ ràng.
Thụy Sĩ

3
Đối với những người muốn tìm mô tả về ý nghĩa ở trên trong trang bash man, hãy tìm phần "Mở rộng tham số" và sau đó cho văn bản này: "Khi không thực hiện mở rộng chuỗi con, sử dụng các biểu mẫu được ghi lại dưới đây, hãy kiểm tra bash cho một tham số không được đặt hoặc null ['null' có nghĩa là chuỗi rỗng]. Việc bỏ qua kết quả dấu hai chấm trong một thử nghiệm chỉ cho một tham số không được đặt (...) $ {tham số: + word}: Sử dụng Giá trị thay thế. Nếu tham số là null hoặc unset, không có gì được thay thế, nếu không thì việc mở rộng từ được thay thế. "
David Tonhofer

2
@Swiss Điều này không rõ ràng cũng không gián tiếp, nhưng thành ngữ. Có lẽ với một lập trình viên không quen thuộc ${+}${-}không rõ ràng, nhưng việc làm quen với các cấu trúc đó là điều cần thiết nếu một người phải là người sử dụng có thẩm quyền của trình bao.
William Pursell

Xem stackoverflow.com/a/20003892/14731 nếu bạn muốn thực hiện điều này với set -o nounsetkích hoạt.
Gili

Tôi đã làm rất nhiều thử nghiệm về điều này; bởi vì kết quả trong kịch bản của tôi không nhất quán Tôi đề nghị dân gian nhìn vào [ -v VAR ]cách tiếp cận "" với bash -s v4.2 và hơn thế nữa .
sẽ

38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z cũng hoạt động cho các biến không xác định. Để phân biệt giữa không xác định và xác định, bạn sử dụng những điều được liệt kê ở đây hoặc, với các giải thích rõ ràng hơn, tại đây .

Cách sạch nhất là sử dụng mở rộng như trong các ví dụ này. Để có được tất cả các tùy chọn của bạn, hãy kiểm tra phần Mở rộng tham số của hướng dẫn.

Từ thay thế:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Giá trị mặc định:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Tất nhiên, bạn sẽ sử dụng một trong những cách khác nhau, đặt giá trị bạn muốn thay vì 'giá trị mặc định' và sử dụng trực tiếp mở rộng, nếu phù hợp.


1
Nhưng tôi muốn phân biệt giữa chuỗi là "" hay chưa được xác định bao giờ. Điều đó có thể không?
Setjmp

1
câu trả lời này cho biết làm thế nào để phân biệt giữa hai trường hợp đó; theo liên kết bash faq nó cung cấp để thảo luận thêm.
Charles Duffy

1
Tôi nói thêm rằng sau bình luận của anh ấy, Charles
Vinko Vrsalovic

2
Tra cứu "Mở rộng tham số" trong trang bash man cho tất cả các "thủ thuật" này. Ví dụ: $ {foo: -default} để sử dụng giá trị mặc định, $ {foo: = default} để gán giá trị mặc định, $ {foo :? Thông báo lỗi} để hiển thị thông báo lỗi nếu foo không được đặt, v.v.
Jouni K Seppänen

18

Hướng dẫn viết kịch bản bash nâng cao, 10.2. Thay thế tham số:

  • $ {var + blahblah}: nếu var được xác định, 'blahblah' được thay thế cho biểu thức, null khác được thay thế
  • $ {var-blahblah}: nếu var được định nghĩa, chính nó được thay thế, 'blahblah' khác được thay thế
  • $ {var? blahblah}: nếu var được xác định, nó được thay thế, nếu không thì hàm tồn tại với 'blahblah' là một thông báo lỗi.


để dựa trên logic chương trình của bạn về việc biến $ mystr có được xác định hay không, bạn có thể thực hiện các thao tác sau:

isdefined=0
${mystr+ export isdefined=1}

bây giờ, nếu isd xác định = 0 thì biến không được xác định, nếu isd xác định = 1 biến được xác định

Cách kiểm tra các biến này tốt hơn câu trả lời ở trên vì nó thanh lịch hơn, dễ đọc hơn và nếu bash shell của bạn được cấu hình thành lỗi khi sử dụng các biến không xác định (set -u), tập lệnh sẽ chấm dứt sớm.


Những thứ hữu ích khác:

để có giá trị mặc định là 7 được gán cho $ mystr nếu nó không được xác định và giữ nguyên giá trị khác:

mystr=${mystr- 7}

để in một thông báo lỗi và thoát khỏi hàm nếu biến không được xác định:

: ${mystr? not defined}

Coi chừng ở đây tôi đã sử dụng ':' để không có nội dung của $ mystr được thực thi như một lệnh trong trường hợp được xác định.


Tôi không biết về? cú pháp cho các biến BASH. Đó là một đánh bắt tốt đẹp.
Thụy Sĩ

Điều này cũng hoạt động với các tham chiếu biến gián tiếp. Điều này vượt qua: var= ; varname=var ; : ${!varname?not defined}và điều này chấm dứt : varname=var ; : ${!varname?not defined}. Nhưng đó là một thói quen tốt để sử dụng set -umà làm như vậy dễ dàng hơn nhiều.
ceving

11

Một bản tóm tắt các bài kiểm tra.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

Thực hành tốt trong bash. Đôi khi, đường dẫn tệp có khoảng trắng hoặc để bảo vệ chống lại lệnh tiêm
k107

1
Tôi nghĩ với ${var+x}nó là không cần thiết trừ khi tên biến được phép có khoảng trắng trong chúng.
Anne van Rossum

Không chỉ thực hành tốt; nó đòi hỏi với [để có được kết quả chính xác. Nếu varkhông được đặt hoặc trống, hãy [ -n $var ]giảm xuống [ -n ]có trạng thái thoát 0, trong khi [ -n "$var" ]có trạng thái thoát dự kiến ​​(và chính xác) là 1.
chepner

5

https://stackoverflow.com/a/9824943/14731 chứa câu trả lời tốt hơn (câu trả lời dễ đọc hơn và hoạt động khi set -o nounsetđược bật). Nó hoạt động đại khái như thế này:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

4

Cách rõ ràng để kiểm tra một biến được xác định sẽ là:

[ -v mystr ]

1
Không thể tìm thấy điều này trong bất kỳ trang man nào (cho bash hoặc test) nhưng nó hoạt động với tôi .. Bạn có biết phiên bản này đã được thêm vào không?
Ash Berlin-Taylor

1
Nó được ghi lại trong Biểu thức có điều kiện , được tham chiếu từ testtoán tử ở phần đuôi của phần Bourne Shell Buildins . Tôi không biết khi nào nó được thêm vào, nhưng nó không có trong phiên bản Apple bash(dựa trên bash3.2.51), vì vậy đây có thể là một tính năng 4.x.
Jonathan Leffler

1
Điều này không làm việc cho tôi với GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). Lỗi là -bash: [: -v: unary operator expected. Mỗi bình luận ở đây , nó yêu cầu tối thiểu bash 4.2 để hoạt động.
Acumenus

Cảm ơn bạn đã kiểm tra khi nó trở nên có sẵn. Tôi đã sử dụng nó trước đây trên các hệ thống khác nhau, nhưng rồi đột nhiên nó không hoạt động. Tôi đến đây vì tò mò và vâng, tất nhiên bash trên hệ thống này là bash 3.x.
clacke

1

tùy chọn khác: mở rộng "danh sách mảng chỉ mục" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

lần duy nhất điều này mở rộng thành chuỗi trống là khi fookhông được đặt, vì vậy bạn có thể kiểm tra nó với chuỗi có điều kiện:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

nên có sẵn trong mọi phiên bản bash> = 3.0


1

không đổ chiếc xe này hơn nữa, nhưng muốn thêm

shopt -s -o nounset

là thứ bạn có thể thêm vào đầu tập lệnh, nó sẽ báo lỗi nếu các biến không được khai báo ở bất kỳ đâu trong tập lệnh. Thông điệp bạn nhìn thấy là unbound variable, nhưng như những người khác đề cập, nó sẽ không bắt được một chuỗi rỗng hoặc giá trị null. Để đảm bảo bất kỳ giá trị riêng lẻ nào không trống, chúng tôi có thể kiểm tra một biến khi nó được mở rộng với ${mystr:?}, còn được gọi là mở rộng ký hiệu đô la, sẽ xảy ra lỗi parameter null or not set.


1

Các tay Bash Reference là một nguồn thông tin xác về bash.

Đây là một ví dụ về kiểm tra một biến để xem nó có tồn tại không:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(Từ mục 6.3.2 .)

Lưu ý rằng khoảng trắng sau khi mở [và trước không phải]tùy chọn .


Mẹo cho người dùng Vim

Tôi đã có một kịch bản có một số tuyên bố như sau:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Nhưng tôi muốn họ trì hoãn mọi giá trị hiện có. Vì vậy, tôi đã viết lại chúng để trông như thế này:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Tôi đã có thể tự động hóa điều này trong vim bằng cách sử dụng regex nhanh:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Điều này có thể được áp dụng bằng cách chọn các dòng có liên quan một cách trực quan, sau đó gõ :. Thanh lệnh được điền trước :'<,'>. Dán lệnh trên và nhấn enter.


Đã thử nghiệm trên phiên bản Vim này:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Người dùng Windows có thể muốn các kết thúc dòng khác nhau.


0

Đây là những gì tôi nghĩ là một cách rõ ràng hơn để kiểm tra nếu một biến được xác định:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Sử dụng nó như sau:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi

1
Viết một chức năng không phải là một ý tưởng tồi; cầu khẩn greplà khủng khiếp Lưu ý rằng bashhỗ trợ ${!var_name}như một cách để nhận giá trị của một biến có tên được chỉ định trong $var_name, do đó name=value; value=1; echo ${name} ${!name}mang lại lợi nhuận value 1.
Jonathan Leffler

0

Một phiên bản ngắn hơn để kiểm tra biến không xác định có thể chỉ đơn giản là:

test -z ${mystr} && echo "mystr is not defined"

-3

cuộc gọi được thiết lập mà không có bất kỳ đối số nào .. nó xuất ra tất cả các vars được xác định ..
những cái cuối cùng trong danh sách sẽ là những cái được xác định trong tập lệnh của bạn ..
vì vậy bạn có thể chuyển đầu ra của nó sang thứ gì đó có thể tìm ra thứ gì được xác định và cái gì không


2
Tôi đã không bỏ phiếu này, nhưng nó là một cái búa tạ để bẻ một hạt khá nhỏ.
Jonathan Leffler

Tôi thực sự thích câu trả lời này tốt hơn câu trả lời được chấp nhận. Rõ ràng những gì đang được thực hiện trong câu trả lời này. Câu trả lời được chấp nhận là vô ích như địa ngục.
Thụy Sĩ

Có vẻ như câu trả lời này là một tác dụng phụ của việc thực hiện "tập hợp" hơn là một điều gì đó mong muốn.
Jonathan Morgan
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.