bash nếu không có nhiều điều kiện mà không có subshell?


23

Tôi muốn kết hợp nhiều điều kiện trong một shell if statement và phủ nhận kết hợp. Tôi có mã làm việc sau đây cho một sự kết hợp đơn giản của các điều kiện:

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ; then
  # do stuff with the files
fi

Điều này hoạt động tốt. Nếu tôi muốn phủ nhận nó, tôi có thể sử dụng mã làm việc sau:

if ! ( [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ) ; then
  echo "Error: You done goofed."
  exit 1
fi
# do stuff with the files

Điều này cũng hoạt động như mong đợi. Tuy nhiên, điều đó xảy ra với tôi rằng tôi không biết dấu ngoặc đơn đang thực sự làm gì ở đó. Tôi muốn sử dụng chúng chỉ để nhóm, nhưng nó thực sự sinh ra một subshell? (Làm thế nào tôi có thể nói?) Nếu vậy, có cách nào để nhóm các điều kiện mà không sinh ra một subshell?


Tôi cho rằng tôi cũng có thể sử dụng logic boolean để xác định biểu thức tương đương: if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thennhưng tôi muốn một câu trả lời tổng quát hơn.
tự đại diện

Bạn cũng có thể phủ nhận bên trong niềng răng, ví dụ:if [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti

1
Vâng, tôi vừa thử nghiệm if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fivà nó hoạt động. Tôi chỉ luôn viết các bài kiểm tra với niềng răng đôi như một vấn đề của thói quen. Ngoài ra, để đảm bảo là không bash, tôi đã thử nghiệm thêm if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fivà nó cũng hoạt động theo cách đó.
DopeGhoti

1
@Wildcard - [ ! -e file/. ] && [ -r file ]sẽ bỏ thư mục. phủ nhận nó như bạn muốn. Tất nhiên, đó là những gì -dlàm.
mikeerv

1
Câu hỏi liên quan (tương tự nhưng không nhiều thảo luận và câu trả lời không hữu ích lắm): unix.stackexchange.com/q/156885/135943
Wildcard

Câu trả lời:


32

Bạn cần sử dụng { list;}thay vì (list):

if ! { [ -f file1 ] && [ -f file2 ] && [ -f file3 ]; }; then
  : do something
fi

Cả hai đều là Nhóm lệnh , nhưng { list;}thực thi các lệnh trong môi trường shell hiện tại.

Lưu ý rằng, ;trong { list;}là cần thiết để phân định danh sách từ từ }đảo ngược, bạn cũng có thể sử dụng dấu phân cách khác. Không gian (hoặc dấu phân cách khác) sau {cũng được yêu cầu.


Cảm ơn bạn! Một cái gì đó làm tôi vấp ngã lúc đầu (vì tôi sử dụng niềng răng xoăn awkthường xuyên hơn bash) là nhu cầu về khoảng trắng sau khi niềng răng mở. Bạn đề cập đến "bạn cũng có thể sử dụng dấu phân cách khác"; ví dụ nào?
tự đại diện

@Wildcard: Có, khoảng trắng sau dấu ngoặc nhọn mở là cần thiết. Một ví dụ cho dấu phân cách khác là một dòng mới, vì bạn thường muốn bạn xác định một hàm.
cuonglm

@don_crissti: Trong zsh, bạn có thể sử dụng khoảng trắng
cuonglm

@don_crissti: &cũng là một dấu phân cách, bạn có thể sử dụng { echo 1;:&}, nhiều dòng mới cũng có thể được sử dụng.
cuonglm

2
Bạn có thể sử dụng bất kỳ trích dẫn metacharacter từ hướng dẫn sử dụng they must be separated from list by whitespace or another shell metacharacter.Trong bash chúng là : metacharacter: | & ; ( ) < > space tab . Đối với nhiệm vụ cụ thể này, tôi tin rằng bất kỳ trong số đó & ; ) space tabsẽ làm việc. .... .... Từ zsh (như một nhận xét) : each sublist is terminated by ; ', &', & |',&!', or a newline.

8

Bạn có thể sử dụng hoàn toàn các testchức năng để đạt được những gì bạn muốn. Từ trang người đàn ông của test:

 ! expression  True if expression is false.
 expression1 -a expression2
               True if both expression1 and expression2 are true.
 expression1 -o expression2
               True if either expression1 or expression2 are true.
 (expression)  True if expression is true.

Vì vậy, tình trạng của bạn có thể trông giống như:

if [ -f file1 -a -f file2 -a -f file3 ] ; then
    # do stuff with the files
fi

Để phủ định sử dụng dấu ngoặc đơn thoát:

if [ ! \( -f file1 -a -f file2 -a -f file3 \) ] ; then
    echo "Error: You done goofed."
    exit 1
fi
# do stuff with the files

6

Để portably phủ nhận một điều kiện phức tạp trong vỏ, bạn có phải áp dụng luật De Morgan và đẩy sự phủ định tất cả các con đường xuống bên trong [các cuộc gọi ...

if [ ! -f file1 ] || [ ! -f file2 ] || [ ! -f file3 ]
then
    # do stuff
fi

... hoặc bạn phải sử dụng then :; else...

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ]
then :
else
  # do stuff
fi

if ! commandlà không có sẵn và cũng không [[.

Nếu bạn không cần tổng số tính di động, đừng viết kịch bản shell . Bạn thực sự có nhiều khả năng tìm thấy /usr/bin/perltrên một Unix được chọn ngẫu nhiên hơn bạn bash.


1
!là POSIX mà ngày nay là đủ di động. Ngay cả các hệ thống vẫn xuất xưởng với vỏ Bourne cũng có một POSIX sh ở một nơi khác trên hệ thống tệp mà bạn có thể sử dụng để diễn giải cú pháp chuẩn của mình.
Stéphane Chazelas

@ StéphaneChazelas Điều này đúng về mặt kỹ thuật nhưng theo kinh nghiệm của tôi, việc viết lại một tập lệnh trong Perl sẽ dễ dàng hơn so với việc giải quyết rắc rối trong việc định vị và kích hoạt môi trường shell tuân thủ POSIX bắt đầu từ đó /bin/sh. Các kịch bản Autoconf làm như bạn đề xuất, nhưng tôi nghĩ tất cả chúng ta đều biết rằng đó là một sự chứng thực ít như thế nào.
zwol

5

những người khác đã lưu ý nhóm {lệnh ghép ;}, nhưng nếu bạn đang thực hiện các thử nghiệm giống hệt nhau trên một bộ, bạn có thể muốn sử dụng một loại khác:

if  ! for f in file1 file2 file3
      do  [ -f "$f" ] || ! break
      done
then  : do stuff
fi

... như đã được chứng minh ở nơi khác { :;}, không có khó khăn nào liên quan đến các lệnh ghép lồng nhau ...

Lưu ý rằng các bài kiểm tra (thông thường) ở trên cho các tệp thông thường . Nếu bạn chỉ tìm kiếm các tệp hiện có , có thể đọc được mà không phải là thư mục:

if  ! for f in file1 file2 file3
      do  [ ! -d "$f" ] && 
          [   -r "$f" ] || ! break
      done
then  : do stuff
fi

Nếu bạn không quan tâm liệu chúng có phải là thư mục hay không:

if  ! command <file1 <file2 <file3
then  : do stuff
fi

... Hoạt động cho bất kỳ tệp nào có thể đọc , có thể truy cập được , nhưng có thể sẽ bị treo đối với các nhà văn năm mươi.


2

Đây không phải là một câu trả lời đầy đủ cho câu hỏi chính của bạn, nhưng tôi nhận thấy rằng bạn đề cập đến kiểm tra ghép (tệp có thể đọc được) trong một bình luận; ví dụ,

if [ -f file1 ] && [ -r file1 ] && [ -f file2 ] && [ -r file2 ] && [ -f file3 ] && [ -r file3 ]

Bạn có thể củng cố điều này một chút bằng cách xác định hàm shell; ví dụ,

readable_file()
{
    [ -f "$1" ]  &&  [ -r "$1" ]
}

Thêm xử lý lỗi vào (ví dụ, [ $# = 1 ]) để nếm thử. Câu iflệnh đầu tiên , ở trên, bây giờ có thể được cô đọng lại

if readable_file file1  &&  readable_file file2  &&  readable_file file3

và bạn có thể rút ngắn điều này hơn nữa bằng cách rút ngắn tên của hàm. Tương tự như vậy, bạn có thể định nghĩa not_readable_file()(hoặc nrfviết tắt) và bao gồm phủ định trong hàm.


Đẹp, nhưng tại sao không chỉ thêm một vòng lặp? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (Disclaimer: Tôi đã kiểm tra mã này và nó hoạt động nhưng nó không phải là một lớp lót tôi đã thêm dấu chấm phẩy để gửi bài ở đây nhưng đã không kiểm tra vị trí của họ..)
Wildcard

1
Điểm tốt. Tôi sẽ nêu lên mối quan tâm nhỏ rằng nó ẩn nhiều logic điều khiển (kết hợp) trong hàm; ai đó đọc kịch bản và thấy if readable_files file1 file2 file3sẽ không biết xem chức năng đang làm AND hoặc OR - mặc dù (a) Tôi đoán nó khá trực quan, (b) nếu bạn không phân phối các kịch bản, nó đủ tốt nếu bạn hiểu nó, và (c) kể từ khi tôi đề nghị viết tắt tên hàm thành tối nghĩa, tôi không có khả năng nói về khả năng đọc. Tiết (Cont'd)
G-Man nói 'Tái lập lại' '

1
(Tiếp theo), BTW, chức năng vẫn ổn theo cách bạn đã viết, nhưng bạn có thể thay thế for file in "$@" ; dobằng for file do- nó mặc định in "$@"và (hơi phản trực giác) ;không chỉ không cần thiết mà còn thực sự nản lòng.
G-Man nói 'Phục hồi Monica'

0

Bạn cũng có thể phủ nhận bên trong các bài kiểm tra niềng răng, vì vậy để sử dụng lại mã gốc của bạn:

if [[ ! -f file1 ]] || [[ ! -f file2 ]] || [[ ! -f file3 ]] ; then
  # do stuff with the files
fi

2
Bạn cần ||thay vì&&
cuonglm

Bài kiểm tra không phải là "có bất kỳ tệp nào không có", bài kiểm tra là " không có tệp nào ở đó". Việc sử dụng ||sẽ thực thi vị ngữ nếu bất kỳ tệp nào không có mặt, không phải nếu và chỉ có tất cả các tệp không có mặt. KHÔNG (A VÀ B VÀ C) giống như (KHÔNG A) VÀ (KHÔNG B) VÀ (KHÔNG C); xem câu hỏi ban đầu
DopeGhoti

@DopeGhoti, cuonglm nói đúng. Đó là nếu không (tất cả các tệp ở đó) vì vậy khi bạn chia nó ra theo cách này bạn cần ||thay vì &&. Xem bình luận đầu tiên của tôi bên dưới câu hỏi của tôi.
tự đại diện

2
@DopeGhoti: not (a and b)giống như (not a) or (not b). Xem en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm

1
Nó phải là thứ năm. Tôi không bao giờ có thể có được thứ năm. Đã sửa, và tôi sẽ để lại chân cho hậu thế.
DopeGhoti

-1

Tôi muốn sử dụng chúng chỉ để nhóm, nhưng nó thực sự sinh ra một subshell? (Làm thế nào tôi có thể nói?)

Có, các lệnh trong (...)được chạy trong một lớp con.

Để kiểm tra xem bạn có ở trong một lớp con hay không, bạn có thể so sánh PID của shell hiện tại của bạn với PID của shell tương tác.

echo $BASHPID; (echo $BASHPID); 

Ví dụ đầu ra, chứng minh rằng dấu ngoặc đơn sinh ra một khung con và dấu ngoặc nhọn không:

$ echo $BASHPID; (echo $BASHPID); { echo $BASHPID;}
50827
50843
50827
$ 

6
()không sinh ra subshell..có ý bạn là {}....
heemayl

@heemayl, đó là một phần khác trong câu hỏi của tôi. Có cách nào để tôi có thể nhìn thấy hoặc chứng minh trên màn hình của mình sự thật về một lớp vỏ được sinh ra không? (Đó thực sự có thể là một câu hỏi riêng biệt nhưng sẽ rất tốt để biết ở đây.)
Wildcard

1
@heemayl Bạn nói đúng! Tôi đã thực hiện echo $$ && ( echo $$ )để so sánh các PID và chúng giống nhau nhưng ngay trước khi tôi gửi bình luận cho bạn biết bạn đã sai tôi đã thử một điều khác. echo $BASHPID && ( echo $BASHPID )cung cấp cho các PID khác nhau
David King

1
@heemayl echo $BASHPID && { echo $BASHPID; }đưa ra cùng một PID như bạn đề xuất sẽ là trường hợp. Cảm ơn sự giáo dục
David King

@DavidKing, cảm ơn bạn đã sử dụng các lệnh kiểm tra $BASHPID, đó là điều tôi đã thiếu.
tự đại diện
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.