Bash nếu [sai]; trả về đúng


151

Đã học bash tuần này và gặp phải khó khăn.

#!/bin/sh

if [ false ]; then
    echo "True"
else
    echo "False"
fi

Điều này sẽ luôn xuất ra True mặc dù điều kiện dường như chỉ ra điều khác. Nếu tôi loại bỏ dấu ngoặc[] thì nó hoạt động, nhưng tôi không hiểu tại sao.


3
Đây là một cái gì đó để giúp bạn bắt đầu.
keyer

10
BTW, tập lệnh bắt đầu #!/bin/shkhông phải là tập lệnh bash - đó là tập lệnh sh POSIX. Ngay cả khi trình thông dịch sh POSIX trên hệ thống của bạn được cung cấp bởi bash, nó sẽ tắt một loạt các tiện ích mở rộng. Nếu bạn muốn viết tập lệnh bash, hãy sử dụng #!/bin/bashhoặc tương đương phù hợp với địa phương của nó ( #!/usr/bin/env bashđể sử dụng trình thông dịch bash đầu tiên trong PATH).
Charles Duffy

Điều này ảnh hưởng [[ false ]]quá.
CMCDragonkai

Câu trả lời:


192

Bạn đang chạy lệnh [(aka test) với đối số "false", không chạy lệnh false. Vì "false" là một chuỗi không trống, nên testlệnh luôn thành công. Để thực sự chạy lệnh, thả [lệnh.

if false; then
   echo "True"
else
   echo "False"
fi

4
Ý tưởng về việc sai là một lệnh, hoặc thậm chí là một chuỗi, có vẻ kỳ lạ đối với tôi, nhưng tôi đoán nó hoạt động. Cảm ơn.
tenmiles

31
bashkhông có kiểu dữ liệu Boolean và vì vậy không có từ khóa nào đại diện cho đúng và sai. Câu iflệnh chỉ kiểm tra xem lệnh bạn đưa ra thành công hay thất bại. Các testlệnh có một biểu thức và thành công nếu expression còn thoả mãn; một chuỗi không trống là một biểu thức đánh giá là đúng, giống như trong hầu hết các ngôn ngữ lập trình khác. falselà một lệnh luôn luôn thất bại. (Bằng cách tương tự, truelà một lệnh luôn luôn thành công.)
chepner

2
if [[ false ]];trả về đúng, quá. Như vậy if [[ 0 ]];. Và if [[ /usr/bin/false ]];cũng không bỏ qua khối. Làm thế nào có thể sai hoặc 0 đánh giá khác không? Chết tiệt này là bực bội. Nếu có vấn đề, OS X 10.8; Bash 3.
jww

7
Đừng nhầm falsevới hằng số Boolean hoặc 0cho một số nguyên; cả hai chỉ là chuỗi bình thường. [[ x ]]tương đương [[ -n x ]]với bất kỳ chuỗi xnào không bắt đầu bằng dấu gạch nối. bashkhông có bất kỳ hằng số Boolean nào trong bất kỳ bối cảnh nào. Các chuỗi trông giống như số nguyên được xử lý như vậy bên trong (( ... ))hoặc với các toán tử như -eqhoặc -ltbên trong [[ ... ]].
chepner

2
@ tbc0, có nhiều mối quan tâm trong tầm tay hơn là cách đọc một cái gì đó. Nếu các biến được đặt theo mã không hoàn toàn dưới sự kiểm soát của bạn (hoặc có thể bị thao túng bên ngoài, có thể là tên tệp bất thường hoặc theo cách khác), if $predicatecó thể dễ dàng bị lạm dụng để thực thi mã thù địch theo cách if [[ $predicate ]]không thể.
Charles Duffy

55

Một mồi Boolean nhanh cho Bash

Các iftuyên bố mất một lệnh như một tham số (như làm &&, ||vv). Mã kết quả số nguyên của lệnh được hiểu là boolean (0 / null = true, 1 / other = false).

Câu testlệnh lấy toán tử và toán hạng làm đối số và trả về mã kết quả có cùng định dạng với if. Một bí danh của testtuyên bố là [, thường được sử dụng ifđể thực hiện các so sánh phức tạp hơn.

Các câu lệnh truefalsekhông làm gì cả và trả về mã kết quả (lần lượt là 0 và 1). Vì vậy, chúng có thể được sử dụng như những chữ boolean trong Bash. Nhưng nếu bạn đặt các câu lệnh ở một nơi mà chúng được hiểu là các chuỗi, bạn sẽ gặp vấn đề. Trong trường hợp của bạn:

if [ foo ]; then ... # "if the string 'foo' is non-empty, return true"
if foo; then ...     # "if the command foo succeeds, return true"

Vì thế:

if [ true  ] ; then echo "This text will always appear." ; fi;
if [ false ] ; then echo "This text will always appear." ; fi;
if true      ; then echo "This text will always appear." ; fi;
if false     ; then echo "This text will never appear."  ; fi;

Điều này tương tự như làm một cái gì đó như echo '$foo'vs.echo "$foo" .

Khi sử dụng testcâu lệnh, kết quả phụ thuộc vào các toán tử được sử dụng.

if [ "$foo" = "$bar" ]   # true if the string values of $foo and $bar are equal
if [ "$foo" -eq "$bar" ] # true if the integer values of $foo and $bar are equal
if [ -f "$foo" ]         # true if $foo is a file that exists (by path)
if [ "$foo" ]            # true if $foo evaluates to a non-empty string
if foo                   # true if foo, as a command/subroutine,
                         # evaluates to true/success (returns 0 or null)

Nói tóm lại , nếu bạn chỉ muốn kiểm tra một cái gì đó là pass / fail (còn gọi là "true" / "false"), thì hãy truyền một lệnh cho câu lệnh ifhoặc &&vv của bạn , không có dấu ngoặc. Để so sánh phức tạp, sử dụng dấu ngoặc với các toán tử thích hợp.

Và vâng, tôi biết không có những điều như một kiểu boolean mẹ đẻ trong Bash, và điều đó if[truevề mặt kỹ thuật "lệnh" chứ không phải "báo cáo"; đây chỉ là một lời giải thích rất cơ bản, chức năng.


1
FYI, nếu bạn muốn sử dụng 1/0 là Đúng / Sai trong mã của mình, điều này sẽ if (( $x )); then echo "Hello"; fihiển thị thông báo cho x=1nhưng không cho x=0hoặc x=(không xác định).
Jonathan H

3

Tôi thấy rằng tôi có thể thực hiện một số logic cơ bản bằng cách chạy một cái gì đó như:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C

6
Người ta có thể , nhưng đó là một ý tưởng thực sự tồi tệ. ( $A && $B )là một hoạt động không hiệu quả đáng ngạc nhiên - bạn đang sinh ra một quy trình con thông qua việc gọi fork(), sau đó tách chuỗi nội dung $Ađể tạo danh sách các phần tử (trong trường hợp này là kích thước một) và đánh giá từng phần tử dưới dạng toàn cầu (trong trường hợp này là một phần tử tự đánh giá nó), sau đó chạy kết quả dưới dạng lệnh (trong trường hợp này là lệnh dựng sẵn).
Charles Duffy

3
Ngoài ra, nếu chuỗi truefalsechuỗi của bạn đến từ một nơi khác, có nguy cơ chúng thực sự có thể chứa nội dung khác truehoặc hoặc falsebạn có thể có hành vi bất ngờ lên đến và bao gồm thực thi lệnh tùy ý.
Charles Duffy

6
Việc viết và - coi chúng là số - an toàn hơn nhiều , hoặc coi một chuỗi rỗng là sai và một chuỗi không trống là đúng. A=1; B=1if (( A && B ))[[ $A && $B ]]
Charles Duffy

3
(lưu ý rằng tôi chỉ sử dụng tên biến all-caps ở trên để tuân theo các quy ước được thiết lập bởi câu trả lời - POSIX thực sự chỉ định rõ ràng tên all-caps được sử dụng cho biến môi trường và nội dung shell, và dự trữ tên viết thường cho việc sử dụng ứng dụng; để tránh xung đột với các môi trường HĐH trong tương lai, đặc biệt là việc đặt biến shell ghi đè lên bất kỳ biến môi trường có tên giống như vậy, luôn luôn lý tưởng để tôn vinh quy ước đó trong các tập lệnh của chính mình).
Charles Duffy

Cảm ơn những hiểu biết!
Rodrigo

2

Sử dụng true / false sẽ loại bỏ một số lộn xộn trong khung ...

#! /bin/bash    
#  true_or_false.bash

[ "$(basename $0)" == "bash" ] && sourced=true || sourced=false

$sourced && echo "SOURCED"
$sourced || echo "CALLED"

# Just an alternate way:
! $sourced  &&  echo "CALLED " ||  echo "SOURCED"

$sourced && return || exit

Tôi thấy điều này hữu ích nhưng trong Ubuntu 18.04 $ 0 là "-bash" và lệnh basename đã gây ra lỗi. Thay đổi bài kiểm tra đó để [[ "$0" =~ "bash" ]] làm cho kịch bản làm việc cho tôi.
WiresHarness 22/11/19
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.