ฉันจะลบล้างมูลค่าคืนของกระบวนการได้อย่างไร


103

ฉันกำลังมองหากระบวนการปฏิเสธที่เรียบง่าย แต่ข้ามแพลตฟอร์มที่ลบล้างคุณค่าที่กระบวนการส่งคืน ควรแมป 0 กับค่าบางค่า! = 0 และค่าใด ๆ ! = 0 ถึง 0 นั่นคือคำสั่งต่อไปนี้ควรส่งคืน "ใช่ไม่มีเส้นทางที่มีอยู่":

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

เดอะ! - ตัวดำเนินการนั้นยอดเยี่ยม แต่น่าเสียดายที่ไม่ขึ้นกับเปลือก


ด้วยความอยากรู้คุณมีระบบเฉพาะในใจที่ไม่มีการทุบตีโดยปริยายหรือไม่ ฉันถือว่า "ข้ามแพลตฟอร์ม" คุณหมายถึง * nix-based เนื่องจากคุณยอมรับคำตอบที่ใช้ได้กับระบบ * nix เท่านั้น
Parthian Shot

คำตอบ:


115

ก่อนหน้านี้มีการนำเสนอคำตอบว่าตอนนี้ส่วนแรกเป็นส่วนสุดท้ายคืออะไร

POSIX Shell มีตัว!ดำเนินการ

เมื่อมองไปรอบ ๆ ข้อกำหนดของเชลล์สำหรับปัญหาอื่น ๆ ฉันเพิ่งสังเกตเห็นว่า POSIX เชลล์รองรับตัว!ดำเนินการ ตัวอย่างเช่นแสดงเป็นคำสงวนและสามารถปรากฏที่จุดเริ่มต้นของไปป์ไลน์โดยที่คำสั่งธรรมดาเป็นกรณีพิเศษของ 'ไปป์ไลน์' ดังนั้นจึงสามารถใช้ในifคำสั่งและwhileหรือuntilวนซ้ำได้เช่นกัน - ในเชลล์ที่สอดคล้องกับ POSIX ดังนั้นแม้จะมีการจองของฉัน แต่ก็น่าจะมีให้บริการในวงกว้างมากกว่าที่ฉันรู้ในปี 2008 การตรวจสอบ POSIX 2004 และ SUS / POSIX 1997 อย่างรวดเร็วแสดงให้เห็นว่า!มีอยู่ในทั้งสองเวอร์ชัน

โปรดสังเกตว่าตัว!ดำเนินการต้องปรากฏที่จุดเริ่มต้นของไปป์ไลน์และลบล้างรหัสสถานะของไปป์ไลน์ทั้งหมด (เช่นคำสั่งสุดท้าย ) นี่คือตัวอย่างบางส่วน.

# 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

คำตอบแบบพกพา - ใช้ได้กับเปลือกหอยโบราณ

ในสคริปต์ Bourne (Korn, POSIX, Bash) ฉันใช้:

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

นี่คือแบบพกพาที่ได้รับ 'คำสั่งและอาร์กิวเมนต์' อาจเป็นไปป์ไลน์หรือลำดับประกอบอื่น ๆ ของคำสั่ง

notคำสั่ง

'!' ตัวดำเนินการไม่ว่าจะติดตั้งในเชลล์ของคุณหรือที่จัดเตรียมโดย o / s จะไม่สามารถใช้งานได้ แม้ว่าจะไม่ยากที่จะเขียน แต่โค้ดด้านล่างนี้มีอายุย้อนกลับไปอย่างน้อยปี 1991 (แม้ว่าฉันคิดว่าฉันเขียนเวอร์ชันก่อนหน้านานกว่านั้น) ฉันไม่มักจะใช้สิ่งนี้ในสคริปต์ของฉันเพราะมันไม่สามารถใช้ได้อย่างน่าเชื่อถือ

/*
@(#)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);
}

สิ่งนี้จะส่งคืน 'ความสำเร็จ' ตรงข้ามกับความล้มเหลวเมื่อไม่สามารถดำเนินการคำสั่งได้ เราสามารถถกเถียงกันได้ว่าตัวเลือก "ไม่ทำอะไรสำเร็จ" นั้นถูกต้องหรือไม่ บางทีควรรายงานข้อผิดพลาดเมื่อไม่ได้ขอให้ทำอะไรเลย รหัสใน ' "stderr.h"' มีสิ่งอำนวยความสะดวกในการรายงานข้อผิดพลาดอย่างง่าย - ฉันใช้ทุกที่ ซอร์สโค้ดตามคำขอ - ดูหน้าโปรไฟล์ของฉันเพื่อติดต่อฉัน


+1 สำหรับ 'คำสั่งและอาร์กิวเมนต์' อาจเป็นไปป์ไลน์หรือลำดับประกอบอื่น ๆ ของคำสั่ง โซลูชันอื่นใช้ไม่ได้กับไปป์ไลน์
HVNSweeting

3
ตัวแปรสภาพแวดล้อม Bash ต้องเป็นไปตามตัว!ดำเนินการ สิ่งนี้ได้ผลสำหรับฉัน:! MY_ENV=value my_command
Daniel Böhmer

1
การปฏิเสธใช้งานได้กับท่อทั้งหมดดังนั้นคุณจึงไม่สามารถทำได้ldd foo.exe | ! grep badlibแต่คุณสามารถทำได้! ldd foo.exe | grep badlibหากคุณต้องการให้สถานะการออกเป็น 0 หากไม่พบbadlib ใน foo.exe ตามความหมายคุณต้องการเปลี่ยนgrepสถานะ แต่การกลับท่อทั้งหมดจะให้ผลลัพธ์เหมือนกัน
Mark Lakata

@MarkLakata: คุณพูดถูก: ดูไวยากรณ์เชลล์ POSIX และส่วนที่เกี่ยวข้อง (ไปที่POSIXเพื่อให้ดูดีที่สุด) แต่ก็เป็นไปได้ที่จะใช้ldd foo.exe | { ! grep badlib; }(แม้ว่ามันจะเป็นความรำคาญโดยเฉพาะอย่างยิ่งอัฒภาค; คุณสามารถใช้ย่อยเปลือกเต็มไปด้วย(และ)และไม่มีอัฒภาค) OTOH วัตถุจะเปลี่ยนสถานะทางออกของไปป์ไลน์และนั่นคือสถานะการออกของคำสั่งสุดท้าย (ยกเว้นใน Bash บางครั้ง)
Jonathan Leffler

3
ตามคำตอบนี้!คำสั่งมีปฏิสัมพันธ์ที่ไม่พึงประสงค์ด้วยset -eเช่นจะทำให้คำสั่งที่จะประสบความสำเร็จกับ 0 หรือที่ไม่ใช่ศูนย์รหัสทางออกแทนการที่ประสบความสำเร็จมีเพียงรหัสทางออกที่ไม่ใช่ศูนย์ มีวิธีที่รัดกุมเพื่อให้ประสบความสำเร็จเพียงด้วยไม่ใช่ศูนย์รหัสทางออก?
Elliott Slaughter


17

คุณสามารถลอง:

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

หรือเพียงแค่:

! ls nonexistingpath

5
น่าเสียดายที่!ใช้กับไม่ได้git bisect runหรืออย่างน้อยฉันก็ไม่รู้วิธี
Attila O.

1
คุณสามารถใส่สิ่งต่างๆลงในเชลล์สคริปต์ได้ตลอดเวลา git bisect run ไม่เห็น!ในกรณีนั้น
toolforger

10

หากเกิดเหตุการณ์ที่คุณไม่มี Bash เป็นเชลล์ของคุณ (เช่น: สคริปต์ git หรือการทดสอบหุ่นเชิด) คุณสามารถเรียกใช้:

echo '! ls notexisting' | bash

-> รหัสใหม่: 0

echo '! ls /' | bash

-> รหัสใหม่: 1


1
ในการใส่เบรดครัมบ์อื่นที่นี่จำเป็นสำหรับบรรทัดในส่วนสคริปต์ของ travis-ci
kgraney

สิ่งนี้จำเป็นสำหรับไปป์ไลน์ BitBucket ด้วย
KumZ

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

หรือ

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

ฉันคัดลอกหากจากคำถามเดิมฉันคิดว่ามันเป็นส่วนหนึ่งของไปป์ไลน์ฉันก็ช้าไปบ้างในบางครั้ง;)
Robert Gamble

คำสั่งทั้งสองนี้เกือบจะเท่ากัน แต่ค่าส่งคืนที่เหลือ$?จะแตกต่างกัน คนแรกอาจปล่อยไว้$?=1หากnonexistingpathมีอยู่จริง $?=0คนที่สองเสมอใบ หากคุณใช้สิ่งนี้ใน Makefile และจำเป็นต้องพึ่งพาค่าที่ส่งคืนคุณต้องระวัง
Mark Lakata

1

หมายเหตุ: !(command || other command)บางครั้งคุณจะเห็น
แค่! ls nonexistingpath && echo "yes, nonexistingpath doesn't exist."นี้ก็พอแล้ว
ไม่จำเป็นต้องมีเปลือกย่อย

Git 2.22 (Q2 2019) แสดงให้เห็นถึงรูปแบบที่ดีกว่าด้วย:

Commit 74ec8cf , กระทำ 3fae7ad , กระทำ 0e67c32 , กระทำ 07353d9 , กระทำ 3bc2702 , กระทำ 8c3b9f7 , กระทำ 80a539a , กระทำ c5c39f4 (13 มีนาคม 2019) โดยSzeder Gábor (szeder )
ดูกระทำ 99e37c2 , กระทำ 9f82b2a , กระทำ 900721e (13 มีนาคม 2019) โดยโยฮันเน Schindelin (dscho )
(รวมโดยJunio C Hamano - gitster-ในการกระทำ 579b75a , 25 เมษายน 2019)

t9811-git-p4-label-import: แก้ไขการปฏิเสธไปป์ไลน์

ใน ' t9811-git-p4-label-import.sh' การทดสอบ ' tag that cannot be exported' ทำงาน:

!(p4 labels | grep GIT_TAG_ON_A_BRANCH)

เพื่อตรวจสอบว่าไม่ได้พิมพ์สตริงที่ระบุโดย " p4 labels"
นี่เป็นปัญหาเนื่องจากตามPOSIX :

"ถ้าไปป์ไลน์ขึ้นต้นด้วยคำสงวน!และcommand1เป็นคำสั่ง subshell แอปพลิเคชันจะต้องตรวจสอบให้แน่ใจว่าตัว(ดำเนินการที่จุดเริ่มต้นcommand1ถูกแยกออกจากอักขระตั้งแต่!หนึ่ง<blank>ตัวขึ้นไป
พฤติกรรมของคำสงวนที่!ตามด้วยตัว(ดำเนินการทันทีจะไม่ระบุ "

ในขณะที่เชลล์ทั่วไปส่วนใหญ่ยังคงตีความสิ่งนี้ว่า ' !' เป็น "ลบล้างรหัสทางออกของคำสั่งสุดท้ายในไปป์ไลน์" แต่mksh/lkshอย่าและตีความเป็นรูปแบบชื่อไฟล์เชิงลบแทน
เป็นผลให้พวกเขาพยายามเรียกใช้คำสั่งที่ประกอบด้วยชื่อพา ธ ในไดเร็กทอรีปัจจุบัน (มีไดเร็กทอรีเดียวที่เรียกว่า ' main') ซึ่งแน่นอนว่าไม่ผ่านการทดสอบ

เราสามารถแก้ไขได้ง่ายๆโดยการเพิ่มช่องว่างระหว่าง ' !' และ ' (' แต่ให้แก้ไขโดยการลบ subshell ที่ไม่จำเป็นออก โดยเฉพาะอย่างยิ่งCommit 74ec8cf


1

วิธีแก้โดยไม่ต้อง! โดยไม่ต้อง subshell โดยไม่ต้องถ้าและอย่างน้อยควรทำงานใน 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."
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.