สถานะการส่งคืนของการกำหนดตัวแปรกำหนดอย่างไร?


10

ฉันได้เห็นโครงสร้างในสคริปต์เช่นนี้

if somevar="$(somecommand 2>/dev/null)"; then
...
fi

เอกสารนี้อยู่ที่ไหนสักแห่ง? สถานะการส่งคืนของตัวแปรถูกกำหนดอย่างไรและเกี่ยวข้องกับการทดแทนคำสั่งอย่างไร (ตัวอย่างเช่นฉันจะได้ผลลัพธ์เดียวกันด้วยif echo "$(somecommand 2>/dev/null)"; then)

คำตอบ:


13

มันเป็นเอกสาร (สำหรับ POSIX) ในส่วน 2.9.1 คำสั่งพื้นฐาน ของ Open Group Base Specifications มีกำแพงข้อความอยู่ที่นั่น ฉันนำความสนใจของคุณไปยังย่อหน้าสุดท้าย:

หากมีชื่อคำสั่งการดำเนินการจะยังคงที่อธิบายไว้ในคำสั่งค้นหาและการดำเนินการ หากไม่มีชื่อคำสั่ง แต่คำสั่งที่มีการทดแทนคำสั่งคำสั่งจะต้องสมบูรณ์ด้วยสถานะออกจากการทดแทนคำสั่งสุดท้ายที่ดำเนินการ มิฉะนั้นคำสั่งจะสมบูรณ์ด้วยสถานะการออกเป็นศูนย์

ตัวอย่างเช่น

   Command                                         Exit Status
$ FOO=BAR                                   0 (but see also the note from icarus, below)
$ FOO=$(bar)                                Exit status from "bar"
$ FOO=$(bar) baz                            Exit status from "baz"
$ foo $(bar)                                Exit status from "foo"

นี่คือวิธีการทุบตีทำงานเช่นกัน แต่ดูส่วน "ไม่ง่าย" ในตอนท้าย

phkในคำถามของเขาการมอบหมายเป็นเหมือนคำสั่งที่มีสถานะออกยกเว้นเมื่อมีการทดแทนคำสั่ง? แนะนำ

... ดูเหมือนว่าการมอบหมายนั้นถือเป็นคำสั่ง ... โดยมีค่าการออกเป็นศูนย์ แต่จะใช้ก่อนที่ด้านขวาของการมอบหมาย (เช่นการแทนที่คำสั่งเรียก…)

นั่นไม่ใช่วิธีที่ดีในการดู โครงการดิบสำหรับการกำหนดสถานะการกลับมาของคำสั่งง่ายๆ (อย่างใดอย่างหนึ่งไม่ได้มี;, &, |, &&หรือ||) เป็น:

  • สแกนบรรทัดจากซ้ายไปขวาจนกว่าจะถึงจุดสิ้นสุดหรือคำสั่ง (โดยทั่วไปคือชื่อโปรแกรม)
  • หากคุณเห็นการกำหนดตัวแปรสถานะการส่งคืนสำหรับบรรทัดอาจเป็น 0
  • หากคุณเห็นการทดแทนคำสั่ง - เช่น$(…)- รับสถานะการออกจากคำสั่งนั้น
  • หากคุณเข้าถึงคำสั่งจริง (ไม่ใช่ในการทดแทนคำสั่ง) ให้ใช้สถานะการออกจากคำสั่งนั้น
  • สถานะการส่งคืนสำหรับสายคือหมายเลขสุดท้ายที่คุณพบ
    การแทนที่คำสั่งเป็นอาร์กิวเมนต์ของคำสั่งเช่นfoo $(bar)จะไม่นับ fooคุณจะได้รับสถานะออกจาก ในการถอดความสัญลักษณ์ของ phkพฤติกรรมที่นี่คือ

    temporary_variable  = EXECUTE( "bar" )
    overall_exit_status = EXECUTE( "foo", temporary_variable )

แต่นี่เป็นเรื่องธรรมดามาก สถานะการคืนสินค้าโดยรวมจาก

A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
เป็นสถานะออกจาก การกำหนดที่เกิดขึ้นหลังจากการกำหนดไม่ได้ตั้งค่าสถานะการออกโดยรวมเป็น 0cmd4E=D=

icarusในคำตอบของเขาสำหรับคำถามของ phk , ยกประเด็นสำคัญ: ตัวแปรสามารถตั้งค่าเป็นอ่านได้อย่างเดียว ย่อหน้าที่สามถึงครั้งสุดท้ายในมาตรา 2.9.1 ของมาตรฐาน POSIXกล่าวว่า

หากการมอบหมายตัวแปรใด ๆ พยายามกำหนดค่าให้กับตัวแปรที่มีการตั้งค่าแอตทริบิวต์แบบอ่านอย่างเดียวในสภาพแวดล้อมเชลล์ปัจจุบัน (ไม่ว่าการมอบหมายนั้นจะเกิดขึ้นในสภาพแวดล้อมนั้น) จะเกิดข้อผิดพลาดในการกำหนดตัวแปร ดูผลที่ตามมาของข้อผิดพลาดของเชลล์สำหรับผลที่ตามมาของข้อผิดพลาดเหล่านี้

ดังนั้นถ้าคุณพูด

readonly A
C=Garfield A=Felix T=Tigger

สถานะกลับคือ 1. มันไม่สำคัญว่าถ้าสตริงGarfield, Felixและ / หรือTigger จะถูกแทนที่ด้วยแทนคำสั่ง (s) - แต่ดูหมายเหตุด้านล่าง

ส่วนที่ 2.8.1 ผลที่ตามมาของข้อผิดพลาดของเชลล์มีข้อความอีกจำนวนหนึ่งและตารางและลงท้ายด้วย

ในทุกกรณีที่แสดงในตารางที่ไม่จำเป็นต้องมีเชลล์เชิงโต้ตอบเพื่อออกจากเชลล์เชลล์จะไม่ทำการประมวลผลคำสั่งเพิ่มเติมที่เกิดข้อผิดพลาดเพิ่มเติม

รายละเอียดบางอย่างสมเหตุสมผล บางคนไม่:

  • A=มอบหมายบางครั้งถูกยกเลิกบรรทัดคำสั่งเป็นที่ประโยคสุดท้ายดูเหมือนว่าจะระบุ ในตัวอย่างด้านบนCถูกตั้งค่าเป็นGarfieldแต่Tไม่ได้ตั้งค่า (และแน่นอนไม่มีทั้ง  A)
  • ในทำนองเดียวกัน รันแต่ไม่ แต่ในรุ่นของฉันทุบตี (ซึ่งรวมถึง 4.1.x และ 4.3.X) ก็ไม่ดำเนินการ (โดยบังเอิญนี่เป็นการตีความหมายของ phk เพิ่มเติมว่ามูลค่าการออกของการมอบหมายใช้ก่อนที่ด้านขวาของการมอบหมาย)C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    cmd2

แต่นี่เป็นความประหลาดใจ:

ในทุบตีรุ่นของฉัน

แบบอ่านอย่างเดียว
C = บางสิ่งบางอย่าง A = บางสิ่งบางอย่าง T = บางสิ่งบางอย่าง cmd 0

ไม่ดำเนินการ โดยเฉพาะอย่างยิ่ง,cmd0

C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 )    cmd 0
รัน และแต่ไม่ (หมายเหตุว่านี้เป็นตรงข้ามของพฤติกรรมของมันเมื่อมีคำสั่งไม่.) และจะกำหนด(เช่นเดียวกับ) ในสภาพแวดล้อมของ ฉันสงสัยว่านี่เป็นข้อผิดพลาดในทุบตีหรือไม่cmd1cmd3cmd2TCcmd0


ไม่ง่ายเลย:

ย่อหน้าแรกของคำตอบนี้อ้างถึง "คำสั่งง่าย ๆ "  สเปคบอกว่า

“ คำสั่งง่าย ๆ ” คือลำดับของการกำหนดตัวแปรและการเปลี่ยนเส้นทางที่เป็นทางเลือกในลำดับใดก็ได้ตามด้วยคำและการเปลี่ยนเส้นทางซึ่งสิ้นสุดลงโดยผู้ควบคุม

ข้อความเหล่านี้เป็นข้อความที่เหมือนในบล็อกตัวอย่างแรกของฉัน:

$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)

สามรายการแรกประกอบด้วยการกำหนดตัวแปรและสามรายการสุดท้ายรวมถึงการแทนที่คำสั่ง

แต่การมอบหมายตัวแปรบางอย่างนั้นไม่ง่ายนัก  ทุบตี (1)พูดว่า

งบที่ได้รับมอบหมายอาจปรากฏเป็นข้อโต้แย้งไปalias, declare, typeset, export, readonlyและlocalในตัวคำสั่ง ( ประกาศคำสั่ง)

สำหรับexport, สเป POSIXกล่าวว่า

ออกจากสถานะ

    0
      ส่งออกชื่อตัวถูกดำเนินการทั้งหมดเรียบร้อยแล้ว
    > 0
      ส่งออกอย่างน้อยหนึ่งชื่อไม่ได้หรือระบุ-pตัวเลือกแล้วเกิดข้อผิดพลาด

และ POSIX ไม่รองรับlocalแต่bash (1)พูดว่า

มันเป็นข้อผิดพลาดที่จะใช้localเมื่อไม่ได้อยู่ในฟังก์ชั่น สถานะการส่งคืนคือ 0 ยกเว้นว่าlocalใช้นอกฟังก์ชั่นมีการระบุชื่อที่ไม่ถูกต้องหรือชื่อเป็นตัวแปรแบบอ่านอย่างเดียว

เมื่ออ่านระหว่างบรรทัดเราจะเห็นคำสั่งประกาศเช่นนั้น

export FOO=$(bar)

และ

local FOO=$(bar)

มีมากขึ้นเช่น

foo $(bar)

ตราบเท่าที่พวกเขาไม่สนใจสถานะออกจากbar และให้คุณออกจากสถานะขึ้นอยู่กับคำสั่งหลัก ( export, localหรือfoo) ดังนั้นเราจึงมีความประหลาด

   Command                                           Exit Status
$ FOO=$(bar)                                    Exit status from "bar"
                                                  (unless FOO is readonly)
$ export FOO=$(bar)                             0 (unless FOO is readonly,
                                                  or other error from “export”)
$ local FOO=$(bar)                              0 (unless FOO is readonly,
                                                  statement is not in a function,
                                                  or other error from “local”)

ซึ่งเราสามารถสาธิตด้วย

$ export FRIDAY=$(date -d tomorrow)
$ echo "FRIDAY   = $FRIDAY, status = $?"
FRIDAY   = Fri, May 04, 2018  8:58:30 PM, status = 0
$ export SATURDAY=$(date -d "day after tomorrow")
date: invalid date ‘day after tomorrow’
$ echo "SATURDAY = $SATURDAY, status = $?"
SATURDAY = , status = 0

และ

myfunc() {
    local x=$(echo "Foo"; true);  echo "x = $x -> $?"
    local y=$(echo "Bar"; false); echo "y = $y -> $?"
    echo -n "BUT! "
    local z; z=$(echo "Baz"; false); echo "z = $z -> $?"
}

$ myfunc
x = Foo -> 0
y = Bar -> 0
BUT! z = Baz -> 1

โชคดีที่ShellCheckจับข้อผิดพลาดและเพิ่มSC2155ซึ่งแนะนำว่า

export foo="$(mycmd)"

ควรเปลี่ยนเป็น

foo=$(mycmd)
export foo

และ

local foo="$(mycmd)"

ควรเปลี่ยนเป็น

local foo
foo=$(mycmd)

1
เยี่ยมมากขอบคุณ! สำหรับคะแนนโบนัสคุณรู้localหรือไม่ว่ามีส่วนเกี่ยวข้องกับเรื่องนี้อย่างไร เช่นlocal foo=$(bar)?
สัญลักษณ์แทน

1
สำหรับตัวอย่างที่สอง (เพียงFOO=$(bar)) ควรทราบว่าในทางทฤษฎีทั้งสถานะการออกและการมอบหมายอาจมีบทบาทดูunix.stackexchange.com/a/341013/117599
phk

1
@ Wildcard: ฉันดีใจที่คุณชอบ ฉันเพิ่งปรับปรุงใหม่อีกครั้ง ส่วนใหญ่ของรุ่นที่คุณเพิ่งอ่านผิด ตราบใดที่คุณอยู่ที่นี่คุณคิดอย่างไร นี่เป็นข้อบกพร่องใน bash ที่A=foo cmdทำงานcmdแม้ว่าAจะเป็นแบบอ่านอย่างเดียวหรือไม่
G-Man กล่าวว่า 'Reinstate Monica'

1
@phk: (1) ทฤษฎีที่น่าสนใจ แต่ฉันไม่แน่ใจว่ามันสมเหตุสมผล ลองดูตัวอย่างอีกครั้งก่อนที่จะมุ่งหน้าไปที่ "ประหลาดใจ" ของฉัน หากAอ่านอย่างเดียวคำสั่งC=value₁ A=value₂ T=value₃จะตั้งค่าCแต่ไม่ใช่T(และแน่นอนAไม่ได้ตั้งค่า) - เชลล์ยกเลิกการประมวลผลบรรทัดคำสั่งโดยไม่สนใจT=value₃เนื่องจากA=value₂เป็นข้อผิดพลาด (2) ขอบคุณสำหรับลิงก์ไปยังคำถามStack Overflow - ฉันโพสต์ความคิดเห็นไว้
G-Man กล่าวว่า 'Reinstate Monica'

1
@Wildcard "สำหรับคะแนนโบนัสคุณรู้หรือไม่ว่าความสัมพันธ์ในท้องถิ่นกับเรื่องนี้เป็นอย่างไร" ใช่ ... local=$(false)มีค่าทางออก0เพราะ It is an error to use local when not within a function. The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.(จากหน้าคน): สิ่งนี้ไม่เพียงพอกับการหมุนรอบตัวเองบนโลกสำหรับอัจฉริยะที่ออกแบบมาอย่างนั้น
David Tonhofer

2

มันเป็นเอกสารใน Bash ( LESS=+/'^SIMPLE COMMAND EXPANSION' bash):

หากไม่มีชื่อคำสั่งเหลืออยู่หลังจากการขยาย ... มิฉะนั้นคำสั่งจะออก ... หากไม่มีการทดแทนคำสั่งคำสั่งจะออกด้วยสถานะเป็นศูนย์

ในคำอื่น ๆ (คำพูดของฉัน):

หากไม่มีชื่อคำสั่งเหลืออยู่หลังจากการขยายและไม่มีการดำเนินการแทนคำสั่งบรรทัดคำสั่งจะออกด้วยสถานะเป็นศูนย์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.