Cách ngắn gọn để kiểm tra các biến môi trường được đặt trong tập lệnh shell Unix là gì?


500

Tôi đã có một vài tập lệnh shell Unix nơi tôi cần kiểm tra xem các biến môi trường nhất định được đặt trước khi tôi bắt đầu thực hiện công cụ, vì vậy tôi thực hiện loại điều này:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

đó là rất nhiều gõ. Có một thành ngữ thanh lịch hơn để kiểm tra rằng một tập hợp các biến môi trường được đặt không?

EDIT: Tôi nên đề cập rằng các biến này không có giá trị mặc định có ý nghĩa - tập lệnh sẽ báo lỗi nếu có bất kỳ không được đặt.


2
Nhiều câu trả lời cho câu hỏi này giống như một cái gì đó bạn sẽ thấy trên Code Golf Stack Exchange .
Daniel Schilling

Câu trả lời:


587

Mở rộng tham số

Câu trả lời rõ ràng là sử dụng một trong các hình thức mở rộng tham số đặc biệt:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

Hoặc, tốt hơn (xem phần trên 'Vị trí của dấu ngoặc kép' bên dưới):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Biến thể đầu tiên (chỉ sử dụng ?) yêu cầu STATE được đặt, nhưng STATE = "" (một chuỗi trống) là OK - không chính xác những gì bạn muốn, nhưng ký hiệu thay thế và cũ hơn.

Biến thể thứ hai (sử dụng :?) yêu cầu DEST được đặt và không trống.

Nếu bạn cung cấp không có tin nhắn, shell sẽ cung cấp một tin nhắn mặc định.

Cấu ${var?}trúc này có thể di chuyển trở lại Phiên bản 7 UNIX và Bourne Shell (1978 hoặc sau đó). Cấu ${var:?}trúc gần đây hơn một chút: Tôi nghĩ rằng nó đã có trong System III UNIX vào khoảng năm 1981, nhưng nó có thể đã có trong PWB UNIX trước đó. Do đó, nó nằm trong Korn Shell và trong vỏ POSIX, bao gồm cả Bash.

Nó thường được ghi lại trong trang man của shell trong phần có tên là Mở rộng tham số . Ví dụ: bashhướng dẫn sử dụng nói:

${parameter:?word}

Lỗi hiển thị nếu Null hoặc Unset. Nếu tham số là null hoặc không được đặt, việc mở rộng từ (hoặc thông báo cho hiệu ứng đó nếu không có từ) được ghi vào lỗi tiêu chuẩn và trình bao, nếu nó không tương tác, sẽ thoát. Mặt khác, giá trị của tham số được thay thế.

Bộ chỉ huy đại tá

Tôi có lẽ nên thêm rằng lệnh dấu hai chấm đơn giản là có các đối số được đánh giá và sau đó thành công. Đây là ký hiệu nhận xét shell gốc (trước ' #' đến cuối dòng). Trong một thời gian dài, các kịch bản shell Bourne có dấu hai chấm là ký tự đầu tiên. Shell C sẽ đọc một tập lệnh và sử dụng ký tự đầu tiên để xác định xem nó dành cho C Shell ( #băm '' hay shell Bourne ( :dấu hai chấm '). Sau đó, hạt nhân đã tham gia vào hành động và thêm hỗ trợ cho ' #!/path/to/program' và vỏ Bourne nhận được ' #', và quy ước đại tràng đã diễn ra bên lề. Nhưng nếu bạn bắt gặp một kịch bản bắt đầu bằng dấu hai chấm, bây giờ bạn sẽ biết tại sao.


Vị trí của dấu ngoặc kép

Blong hỏi trong một bình luận :

Bất kỳ suy nghĩ về cuộc thảo luận này? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

Ý chính của cuộc thảo luận là:

Tuy nhiên, khi tôi shellcheck(với phiên bản 0.4.1), tôi nhận được thông báo này:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and word splitting.

Bất cứ lời khuyên về những gì tôi nên làm trong trường hợp này?

Câu trả lời ngắn gọn là "làm như shellcheckgợi ý":

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Để minh họa tại sao, nghiên cứu sau đây. Lưu ý rằng :lệnh không lặp lại các đối số của nó (nhưng shell sẽ đánh giá các đối số). Chúng tôi muốn xem các đối số, vì vậy mã dưới đây sử dụng printf "%s\n"thay cho :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Lưu ý cách giá trị trong $xđược mở rộng thành đầu tiên *và sau đó là danh sách tên tệp khi biểu thức tổng thể không nằm trong dấu ngoặc kép. Đây là những gì shellcheckđược khuyến nghị nên được sửa chữa. Tôi đã không xác minh rằng nó không phản đối biểu mẫu trong đó biểu thức được đặt trong dấu ngoặc kép, nhưng đó là một giả định hợp lý rằng nó sẽ ổn.


2
Đó là thứ tôi cần. Tôi đã sử dụng nhiều phiên bản Unix khác nhau từ năm 1987 và tôi chưa bao giờ thấy cú pháp này - chỉ cần hiển thị ...
AndrewR

Làm thế nào để nó hoạt động và nó được ghi nhận ở đâu? Tôi tự hỏi nếu nó có thể được sửa đổi để kiểm tra biến tồn tại và được đặt thành một giá trị cụ thể.
jhabbott

6
Nó được ghi lại trong trang hướng dẫn sử dụng shell hoặc hướng dẫn Bash, thường nằm dưới tiêu đề 'Mở rộng tham số'. Cách tiêu chuẩn để kiểm tra xem biến có tồn tại hay không và được đặt thành một giá trị cụ thể (giả sử 'abc') chỉ đơn giản là : if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
Jonathan Leffler

Làm cách nào để sử dụng cú pháp này với biến chuỗi có khoảng trắng trong đó. Tôi gặp lỗi khi nói "Điều này: không tìm thấy lệnh" khi tôi đặt biến tôi muốn kiểm tra thành "Đây là bài kiểm tra". Có ý kiến ​​gì không?
dùng2294382

1
Bất kỳ suy nghĩ về cuộc thảo luận này? github.com/koalaman/shellcheck/issues/ từ
blong

138

Thử cái này:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;

8
Điều đó khá dài dòng. Các dấu chấm phẩy là dư thừa.
Jonathan Leffler

3
Hoặc thậm chí ngắn hơn [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } xem bài đăng trên blog này
zipizap

36
Đó là những gì có thể đọc được. Tôi là tất cả cho cái này. Kịch bản Bash cần tất cả sự giúp đỡ họ có thể nhận được trong bộ phận đó!
Iain Duncan

câu trả lời ban đầu phù hợp hơn trong cú pháp của nó.
răng nhanh

4
Tuy nhiên, điều này không phân biệt giữa một biến unset và một biến được đặt thành chuỗi rỗng.
chepner

57

Câu hỏi của bạn phụ thuộc vào vỏ mà bạn đang sử dụng.

Vỏ Bourne để lại rất ít theo cách của những gì bạn đang theo đuổi.

NHƯNG...

Nó hoạt động, chỉ là về mọi nơi.

Chỉ cần cố gắng và tránh xa csh. Nó tốt cho tiếng chuông và còi mà nó thêm vào, so với vỏ Bourne, nhưng nó thực sự đang kêu. Nếu bạn không tin tôi, chỉ cần thử và tách STDERR trong csh! (-:

Có hai khả năng ở đây. Ví dụ trên, cụ thể là sử dụng:

${MyVariable:=SomeDefault}

lần đầu tiên bạn cần tham khảo $ MyVariable. Điều này có env. var MyVariable và, nếu hiện tại nó không được đặt, gán giá trị của someDefault cho biến để sử dụng sau.

Bạn cũng có khả năng:

${MyVariable:-SomeDefault}

mà chỉ thay thế someDefault cho biến mà bạn đang sử dụng cấu trúc này. Nó không gán giá trị someDefault cho biến và giá trị của MyVariable sẽ vẫn là null sau khi gặp câu lệnh này.


3
CSH: (foo> foo.out)> & foo.err
Mr.Ree

Vỏ Bourne làm những gì được yêu cầu.
Jonathan Leffler

51

Chắc chắn cách tiếp cận đơn giản nhất là thêm công -utắc vào shebang (dòng ở đầu tập lệnh của bạn), giả sử bạn đang sử dụng bash:

#!/bin/sh -u

Điều này sẽ khiến tập lệnh thoát ra nếu có bất kỳ biến không liên kết nào ẩn trong.


40
${MyVariable:=SomeDefault}

Nếu MyVariableđược đặt và không null, nó sẽ đặt lại giá trị biến (= không có gì xảy ra).
Khác, MyVariableđược đặt thành SomeDefault.

Ở trên sẽ cố gắng thực thi ${MyVariable}, vì vậy nếu bạn chỉ muốn đặt biến thì:

MyVariable=${MyVariable:=SomeDefault}

Điều này không tạo ra thông báo - và Q nói rằng không có mặc định (mặc dù nó không nói rằng khi bạn gõ câu trả lời của bạn).
Jonathan Leffler

10
Các phiên bản chính xác: (1): $ {MyVariable: = someDefault} hoặc (2): MyVariable = $ {MyVariable: -SomeDefault}
Jonathan Leffler

23

Theo tôi, kiểm tra đơn giản và tương thích nhất cho #! / Bin / sh là:

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Một lần nữa, đây là cho / bin / sh và cũng tương thích trên các hệ thống Solaris cũ.


Điều này 'hoạt động', nhưng ngay cả các hệ thống Solaris cũ cũng có một ký hiệu /bin/shhỗ trợ ${var:?}ký hiệu, v.v.
Jonathan Leffler

Câu trả lời tốt nhất cho đến nay.
Ole

4
Được đặt thành chuỗi trống khác với việc không được đặt ở tất cả. Thử nghiệm này sẽ in "Không tồn tại" sau cả hai unset MYVARMYVAR=.
chepner

@chepner, tôi đã nâng cao nhận xét của bạn cho phần đầu có giá trị, nhưng sau đó tôi đã tiếp tục thử nghiệm nó (với bash), và thực sự không thể đặt var thành một chuỗi trống mà không bị bỏ qua. Bạn làm điều đó như thế nào?
Sz.

@Sz Tôi không chắc ý của bạn là gì. "Unset" có nghĩa là tên không có giá trị nào được gán cho nó. Nếu fookhông được đặt, "$foo"vẫn mở rộng thành chuỗi trống.
chepner

17

bash4.2 đã giới thiệu -vtoán tử kiểm tra xem tên có được đặt thành bất kỳ giá trị nào không , ngay cả chuỗi trống.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set

16

Tôi luôn luôn sử dụng:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

Không ngắn gọn hơn nhiều, tôi sợ.

Theo CSH, bạn có $? NHÀ NƯỚC.


2
Điều này kiểm tra nếu STATEtrống hoặc không đặt, không chỉ không đặt.
chepner

7

Đối với những người tương lai như tôi, tôi muốn tiến lên một bước và tham số hóa tên var, vì vậy tôi có thể lặp qua danh sách các tên biến có kích thước thay đổi:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done

3

Chúng ta có thể viết một xác nhận tốt để kiểm tra một loạt các biến cùng một lúc:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Yêu cầu mẫu:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Đầu ra:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set

Thêm các xác nhận như vậy ở đây: https://github.com/codeforester/base/blob/master/lib/assertions.sh



1

Không có giải pháp nào ở trên hoạt động cho mục đích của tôi, một phần vì tôi kiểm tra môi trường để biết danh sách các biến cần mở được đặt trước khi bắt đầu một quy trình dài. Tôi đã kết thúc với điều này:

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}

-1

Thay vì sử dụng các tập lệnh shell bên ngoài, tôi có xu hướng tải các hàm trong shell đăng nhập của mình. Tôi sử dụng một cái gì đó như thế này như là một hàm trợ giúp để kiểm tra các biến môi trường chứ không phải bất kỳ biến thiết lập nào:

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }

1
Cái này sai. is_this_an_env_variable Psẽ trở lại thành công vì PATHtồn tại.
Eric

Nó tồn tại bởi vì nó là một biến môi trường. Cho dù đó là chuỗi eqaul nonnull hay không là một vấn đề khác.
nafdef

-7

Các $?cú pháp là khá gọn gàng:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi

Điều này hoạt động, nhưng có ai ở đây biết nơi tôi có thể tìm tài liệu về cú pháp đó không? $? thường là giá trị trả về trước đó.
Daniel Staple

Xấu của tôi - điều này dường như không hoạt động. Hãy thử nó trong một kịch bản đơn giản, có và không có bộ đó.
Daniel Staple

11
Trong các shell Bourne, Korn, Bash và POSIX, $?là trạng thái thoát của lệnh trước đó; $?BLAHlà chuỗi bao gồm trạng thái thoát của lệnh trước được nối với 'BLAH'. Điều này không kiểm tra các biến $BLAHở tất cả. (Có một tin đồn nó có thể làm ít nhiều những gì được yêu cầu csh, nhưng vỏ sò biển được để lại tốt nhất trên bờ biển.)
Jonathan Leffler

Đây là một câu trả lời hoàn toàn sai khi có liên quan đến Bash.
codeforester
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.