Làm cách nào để phủ định giá trị trả về của một quy trình?


101

Tôi đang tìm kiếm một quy trình phủ định đơn giản nhưng đa nền tảng phủ định giá trị mà một quy trình trả về. Nó sẽ ánh xạ 0 thành một số giá trị! = 0 và bất kỳ giá trị nào! = 0 thành 0, tức là lệnh sau sẽ trả về "yes, nonexistingpath không tồn tại":

 ls nonexistingpath | negate && echo "yes, nonexistingpath doesn't exist."

Các ! - toán tử là tuyệt vời nhưng tiếc là không độc lập với shell.


Vì tò mò, bạn có nghĩ đến một hệ thống cụ thể không bao gồm bash theo mặc định không? Tôi giả định bằng "đa nền tảng" bạn chỉ muốn nói đến dựa trên * nix, vì bạn đã chấp nhận một câu trả lời sẽ chỉ hoạt động trên hệ thống * nix.
Shot Parthia

Câu trả lời:


110

Trước đây, câu trả lời đã được trình bày với những gì bây giờ là phần đầu tiên là phần cuối cùng.

POSIX Shell bao gồm một !toán tử

Xem xét đặc điểm kỹ thuật shell cho các vấn đề khác, gần đây tôi (tháng 9 năm 2015) nhận thấy rằng shell POSIX hỗ trợ một !toán tử. Ví dụ, nó được liệt kê là một từ dành riêng và có thể xuất hiện ở đầu một đường dẫn - trong đó một lệnh đơn giản là một trường hợp đặc biệt của 'đường ống'. Do đó, nó có thể được sử dụng trong các ifcâu lệnh và whilehoặc untilcác vòng lặp - trong các shell tuân thủ POSIX. Do đó, bất chấp sự dè dặt của tôi, nó có lẽ được phổ biến rộng rãi hơn so với những gì tôi nhận ra vào năm 2008. Kiểm tra nhanh POSIX 2004 và SUS / POSIX 1997 cho thấy nó !có mặt trong cả hai phiên bản đó.

Lưu ý rằng !toán tử phải xuất hiện ở đầu đường ống và phủ định mã trạng thái của toàn bộ đường ống (tức là lệnh cuối cùng ). Đây là một số ví dụ.

# Simple commands, pipes, and redirects work fine.
$ ! some-command succeed; echo $?
1
$ ! some-command fail | some-other-command fail; echo $?
0
$ ! some-command < succeed.txt; echo $?
1

# Environment variables also work, but must come after the !.
$ ! RESULT=fail some-command; echo $?
0

# A more complex example.
$ if ! some-command < input.txt | grep Success > /dev/null; then echo 'Failure!'; recover-command; mv input.txt input-failed.txt; fi
Failure!
$ ls *.txt
input-failed.txt

Câu trả lời di động - hoạt động với vỏ cổ

Trong tập lệnh Bourne (Korn, POSIX, Bash), tôi sử dụng:

if ...command and arguments...
then : it succeeded
else : it failed
fi

Điều này là di động như nó được. 'Lệnh và các đối số' có thể là một đường ống dẫn hoặc chuỗi lệnh phức hợp khác.

Một notlệnh

Các '!' nhà điều hành, cho dù được tích hợp sẵn trong trình bao của bạn hay được cung cấp bởi o / s, đều không khả dụng trên toàn cầu. Tuy nhiên, nó không quá khó để viết - đoạn mã dưới đây có từ ít nhất là năm 1991 (mặc dù tôi nghĩ rằng tôi đã viết một phiên bản trước đó thậm chí lâu hơn). Tuy nhiên, tôi không có xu hướng sử dụng điều này trong các tập lệnh của mình, vì nó không có sẵn một cách đáng tin cậy.

/*
@(#)File:           $RCSfile: not.c,v $
@(#)Version:        $Revision: 4.2 $
@(#)Last changed:   $Date: 2005/06/22 19:44:07 $
@(#)Purpose:        Invert success/failure status of command
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1991,1997,2005
*/

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"

#ifndef lint
static const char sccs[] = "@(#)$Id: not.c,v 4.2 2005/06/22 19:44:07 jleffler Exp $";
#endif

int main(int argc, char **argv)
{
    int             pid;
    int             corpse;
    int             status;

    err_setarg0(argv[0]);

    if (argc <= 1)
    {
            /* Nothing to execute. Nothing executed successfully. */
            /* Inverted exit condition is non-zero */
            exit(1);
    }

    if ((pid = fork()) < 0)
            err_syserr("failed to fork\n");

    if (pid == 0)
    {
            /* Child: execute command using PATH etc. */
            execvp(argv[1], &argv[1]);
            err_syserr("failed to execute command %s\n", argv[1]);
            /* NOTREACHED */
    }

    /* Parent */
    while ((corpse = wait(&status)) > 0)
    {
            if (corpse == pid)
            {
                    /* Status contains exit status of child. */
                    /* If exit status of child is zero, it succeeded, and we should
                       exit with a non-zero status */
                    /* If exit status of child is non-zero, if failed and we should
                       exit with zero status */
                    exit(status == 0);
                    /* NOTREACHED */
            }
    }

    /* Failed to receive notification of child's death -- assume it failed */
    return (0);
}

Điều này trả về 'thành công', ngược lại với thất bại, khi nó không thực hiện được lệnh. Chúng ta có thể tranh luận về việc liệu tùy chọn 'không làm gì thành công' có đúng không; có thể nó sẽ báo lỗi khi nó không được yêu cầu làm bất cứ điều gì. Mã trong ' "stderr.h"' cung cấp các phương tiện báo cáo lỗi đơn giản - tôi sử dụng nó ở mọi nơi. Mã nguồn theo yêu cầu - xem trang hồ sơ của tôi để liên hệ với tôi.


+1 cho 'Lệnh và các đối số' có thể là một đường dẫn hoặc chuỗi lệnh phức hợp khác. Các giải pháp khác không có tác dụng với đường ống
HVNSweeting

3
Các biến môi trường bash phải tuân theo !toán tử. Điều này đã làm việc cho tôi:! MY_ENV=value my_command
Daniel Böhmer

1
Phủ định hoạt động trên toàn bộ đường ống, vì vậy bạn không thể làm ldd foo.exe | ! grep badlibnhưng bạn có thể làm ! ldd foo.exe | grep badlibnếu bạn muốn trạng thái thoát là 0 nếu không tìm thấy badlib trong foo.exe. Về mặt ngữ nghĩa, bạn muốn đảo ngược greptrạng thái, nhưng đảo ngược toàn bộ đường ống sẽ cho kết quả tương tự.
Mark Lakata

@MarkLakata: Bạn nói đúng: hãy xem cú pháp shell POSIX và các phần liên quan (truy cập POSIX để có giao diện tốt nhất). Tuy nhiên, nó là khả thi để sử dụng ldd foo.exe | { ! grep badlib; }(mặc dù nó là một phiền toái, đặc biệt là dấu chấm phẩy, bạn có thể sử dụng đầy đủ phụ vỏ với ()và không có dấu chấm phẩy). OTOH, đối tượng là đảo ngược trạng thái thoát của đường ống, và đó là trạng thái thoát của lệnh cuối cùng (đôi khi ngoại trừ trong Bash).
Jonathan Leffler

3
Theo câu trả lời này , !lệnh có một tương tác không mong muốn set -e, tức là nó khiến lệnh thành công với mã thoát 0 hoặc khác 0, thay vì chỉ thành công với mã thoát khác 0. Có cách nào ngắn gọn để làm cho nó thành công chỉ với một mã thoát khác 0 không?
Elliott Slaughter,

40

Trong Bash, sử dụng! toán tử trước lệnh. Ví dụ:

! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist"

17

Bạn có thể thử:

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."

hoặc chỉ:

! ls nonexistingpath

5
Thật không may là không !thể được sử dụng với git bisect run, hoặc ít nhất là tôi không biết làm thế nào.
Attila O.

1
Bạn luôn có thể đưa mọi thứ vào một tập lệnh shell; git bisect run không thấy !trong trường hợp đó.
toolforger

10

Nếu bằng cách nào đó xảy ra rằng bạn không có Bash làm trình bao của mình (ví dụ: git scripts hoặc các bài kiểm tra thực thi con rối), bạn có thể chạy:

echo '! ls notexisting' | bash

-> mã lại: 0

echo '! ls /' | bash

-> mã lại: 1


1
Để đặt một breadcrumb khác ở đây, điều này là cần thiết cho các dòng trong phần script của travis-ci.
kgraney

Điều này cũng cần thiết cho các đường ống dẫn BitBucket.
KumZ

3
! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."

hoặc là

ls nonexistingpath || echo "yes, nonexistingpath doesn't exist."

Tôi sao chép nếu từ câu hỏi ban đầu, tôi nghĩ rằng nó là một phần của đường ống, Tôi là một chút chậm đôi khi;)
Robert Gamble

Hai câu lệnh này gần như tương đương, NHƯNG giá trị trả về còn lại $?sẽ khác nhau. Người đầu tiên có thể rời đi $?=1nếu nonexistingpaththực sự tồn tại. Chiếc thứ hai luôn rời đi $?=0. Nếu bạn đang sử dụng điều này trong Makefile và cần dựa vào giá trị trả về, bạn phải cẩn thận.
Mark Lakata

1

Lưu ý: đôi khi bạn sẽ thấy !(command || other command).
Đây ! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."là đủ.
Không cần vỏ phụ.

Git 2.22 (Quý 2 năm 2019) minh họa hình thức tốt hơn đó với:

Cam kết 74ec8cf , cam kết 3fae7ad , cam kết 0e67c32 , cam kết 07353d9 , cam kết 3bc2702 , cam kết 8c3b9f7 , cam kết 80a539a , cam kết c5c39f4 (13/03/2019) bởi SZEDER Gábor ( szeder) .
Xem cam kết 99e37c2 , cam kết 9f82b2a , cam kết 900721e (ngày 13 tháng 3 năm 2019) bởi Johannes Schindelin ( dscho) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 579b75a , ngày 25 tháng 4 năm 2019)

t9811-git-p4-label-import: sửa lỗi phủ định đường ống

Trong ' t9811-git-p4-label-import.sh', bài kiểm tra ' tag that cannot be exported' chạy:

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

để kiểm tra xem chuỗi đã cho không được in bởi ' p4 labels'.
Đây là vấn đề, bởi vì theo POSIX :

"Nếu đường dẫn bắt đầu bằng từ dành riêng !command1là lệnh vỏ con, ứng dụng sẽ đảm bảo rằng (toán tử ở đầu của command1được phân tách với !một hoặc nhiều <blank>ký tự.
Hành vi của từ dành riêng !ngay sau (toán tử là không xác định. "

Trong khi hầu hết các trình bao phổ biến vẫn diễn giải điều này ' !' là "phủ định mã thoát của lệnh cuối cùng trong đường dẫn", ' mksh/lksh' không và thay vào đó giải thích nó như một mẫu tên tệp phủ định.
Kết quả là, họ cố gắng chạy một lệnh được tạo thành từ các tên đường dẫn trong thư mục hiện tại (nó chứa một thư mục duy nhất có tên là ' main'), tất nhiên, không thể kiểm tra được.

Chúng tôi có thể sửa nó đơn giản bằng cách thêm một khoảng trắng giữa ' !' và ' (', nhưng thay vào đó hãy sửa nó bằng cách loại bỏ vỏ con không cần thiết. Đặc biệt, Cam kết 74ec8cf


1

Giải pháp không có !, không có vỏ con, không có if và ít nhất sẽ hoạt động trong bash:

ls nonexistingpath; test $? -eq 2 && echo "yes, nonexistingpath doesn't exist."

# alternatively without error message and handling of any error code > 0
ls nonexistingpath 2>/dev/null; test $? -gt 0 && echo "yes, nonexistingpath doesn't exist."
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.