ถ้ามีอย่างอื่นคำสั่งเทียบเท่ากับตรรกะและ && หรือ || และฉันจะเลือกอันไหนดีกว่ากัน


27

ฉันเรียนรู้เกี่ยวกับโครงสร้างการตัดสินใจและฉันเจอรหัสเหล่านี้:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

พวกเขาทั้งสองทำงานเหมือนกัน มีข้อได้เปรียบอะไรบ้างในการใช้งานจากอีกทางหนึ่ง?



3
พวกเขาจะไม่เทียบเท่า ดูคำตอบที่ยอดเยี่ยมโดยอิคารัส ตัวอย่างเช่นให้พิจารณากรณีที่มีไฟล์. /my อยู่ แต่ไม่สามารถอ่านได้
AlexP


คำตอบ:


26

ไม่มีการก่อสร้างif A; then B; else C; fiและA && B || Cมีไม่เทียบเท่า

ด้วยif A; then B; else C; fi, คำสั่งAจะถูกประเมินและดำเนินการเสมอ (อย่างน้อยที่สุดความพยายามในการเรียกใช้งานถูกสร้างขึ้นมา) จากนั้นคำสั่งBหรือคำสั่งอย่างใดอย่างหนึ่งCจะถูกประเมินและดำเนินการ

ด้วยA && B || Cมันเป็นเรื่องเดียวกันสำหรับคำสั่งAและBแต่แตกต่างกันสำหรับC: คำสั่งCจะถูกประเมินและดำเนินการถ้าอย่างใดอย่างหนึ่ง Aล้มเหลวหรือ Bล้มเหลว

ในตัวอย่างของคุณสมมติว่าคุณchmod u-r ./myfileแล้วแม้จะ[ -f ./myfile ]ประสบความสำเร็จคุณจะcat /home/user/myfile

คำแนะนำของฉัน: ใช้A && BหรือA || Bสิ่งที่คุณต้องการมันยังอ่านและเข้าใจได้ง่ายและไม่มีกับดัก แต่ถ้าคุณหมายถึงถ้า ... แล้ว ... อื่น ... if A; then B; else C; fiแล้วการใช้งาน


29

คนส่วนใหญ่พบว่ามันง่ายที่จะเข้าใจif... then... else... fiรูปแบบ

สำหรับa && b || cคุณจะต้องแน่ใจว่าbผลตอบแทนที่แท้จริง นี่คือสาเหตุของข้อบกพร่องที่บอบบางและเป็นเหตุผลที่ดีที่จะหลีกเลี่ยงสไตล์นี้ ถ้า b ไม่คืนจริงสิ่งเหล่านี้จะไม่เหมือนกัน

 $ if true; then false ; else echo boom ; fi
 $ true && false || echo boom
 boom

สำหรับการทดสอบสั้น ๆ และการกระทำที่ไม่มีข้ออื่นความยาวที่สั้นลงนั้นน่าดึงดูดใจเช่น

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&&และ||เป็นshort circuiting operatorsเร็วที่สุดเท่าที่ผลการทดสอบเป็นที่รู้จักกันที่ไม่จำเป็นต่อไปจะถูกข้าม จะถูกจัดกลุ่มเป็นa && b || c (a && b) || cครั้งแรกaคือการทำงาน หากสถานะดังกล่าวfailsถูกกำหนดว่าไม่ส่งคืนสถานะการออกเป็น 0 กลุ่ม(a && b)จะทราบfailและbไม่จำเป็นต้องถูกเรียกใช้ ไม่ทราบผลของการแสดงออกเพื่อตอบสนองความต้องการในการดำเนินการ|| cหากaสำเร็จ (คืนค่าศูนย์) &&ผู้ดำเนินการยังไม่ทราบผลลัพธ์a && bดังนั้นbต้องค้นหา หากbประสบความสำเร็จแล้วa && bประสบความสำเร็จและรู้ผลการดำเนินงานประสบความสำเร็จดังนั้นไม่จำเป็นต้องวิ่ง|| cหากbล้มเหลวแล้ว||cยังคงไม่ได้รู้ค่าของการแสดงออกจึงไม่จำเป็นต้องใช้


7

ผู้ประกอบการ&&ดำเนินการคำสั่งถัดไปหากคำสั่งก่อนหน้ามีการดำเนินการที่ประสบความสำเร็จ (ส่งคืนรหัสออก ($?) 0 = ตรรกะจริง)

ในรูปแบบA && B || Cคำสั่ง (หรือเงื่อนไข) Aได้รับการประเมินและหากAคืนค่าจริง (สำเร็จให้ออกรหัส 0) จากนั้นคำสั่งBจะถูกดำเนินการ ถ้าAล้มเหลว (ดังนั้นจะส่งคืนfalse - exit code นอกเหนือจาก 0) และ / หรือBล้มเหลว ( return false ) ดังนั้นคำสั่งCจะถูกดำเนินการ

&&ตัวดำเนินการยังถูกใช้เป็นANDในการตรวจสอบเงื่อนไขและตัวดำเนินการ||ทำงานเช่นORในการตรวจสอบเงื่อนไข

ทั้งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำอย่างไรกับสคริปต์ของคุณในรูปแบบA && B || Cที่สามารถใช้สำหรับการตรวจสอบสภาพเหมือนตัวอย่างของคุณหรือสามารถใช้คำสั่งโซ่และให้แน่ใจว่าชุดของคำสั่งที่จะดำเนินการถ้าคำสั่งก่อนหน้านี้มีรหัสทางออกที่ประสบความสำเร็จ0 นี่คือเหตุผลที่มันเป็นเรื่องธรรมดาที่จะเห็นคำสั่งเช่น:

do_something && do_something_else_that_depended_on_something

ตัวอย่าง:
apt-get update && apt-get upgrade หากการอัปเดตล้มเหลวการอัปเกรดจะไม่ดำเนินการ (สมเหตุสมผลในโลกแห่งความเป็นจริง ... )

mkdir test && echo "Something" > test/file
ส่วนที่echo "Something"จะดำเนินการเฉพาะในกรณีที่mkdir testประสบความสำเร็จและการดำเนินงานกลับรหัสออกจาก0

./configure --prefix=/usr && make && sudo make install
มักจะพบในการรวบรวมงานเพื่อเชื่อมโยงคำสั่งที่ขึ้นอยู่กับความจำเป็นเข้าด้วยกัน

หากคุณพยายามที่จะใช้ "โซ่" ข้างต้นด้วยถ้า - แล้ว - อื่น ๆคุณจะต้องใช้คำสั่งและการตรวจสอบมากขึ้น (และทำให้โค้ดมากขึ้นในการเขียน - สิ่งอื่น ๆ ที่ผิดพลาด) สำหรับงานง่าย

นอกจากนี้โปรดทราบว่าคำสั่งที่ผูกมัดด้วย&&และ|| ถูกอ่านโดยเชลล์จากซ้ายไปขวา คุณอาจต้องจัดกลุ่มคำสั่งและตรวจสอบเงื่อนไขด้วยเครื่องหมายวงเล็บเพื่อขึ้นอยู่กับขั้นตอนถัดไปของเอาต์พุตที่สำเร็จของคำสั่งก่อนหน้าบางคำสั่ง ตัวอย่างเช่นดูนี้:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

หรือตัวอย่างชีวิตจริง:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

โปรดทราบว่าคำสั่งบางคำสั่งจะส่งคืนรหัสทางออกที่แตกต่างกันขึ้นอยู่กับกระบวนการที่ดำเนินการหรือส่งคืนรหัสที่แตกต่างกันขึ้นอยู่กับการกระทำของพวกเขา (เช่นคำสั่ง GNU diffส่งคืน1หากไฟล์สองไฟล์แตกต่างกันและ0หากไม่มี) คำสั่งดังกล่าวจะต้องได้รับการปฏิบัติด้วยความระมัดระวังใน&&และ|| .

นอกจากนี้เพื่อให้ไขปริศนาทั้งหมดเข้าด้วยกันให้คำนึงถึงการรวมคำสั่งเข้าด้วยกันโดยใช้;โอเปอเรเตอร์ ด้วยรูปแบบA;B;Cคำสั่งทั้งหมดจะถูกดำเนินการในซีรีส์ไม่ว่าสิ่งที่เป็นรหัสทางออกของคำสั่งและAB


1

มากของความสับสนเกี่ยวกับเรื่องนี้อาจจะเป็นเพราะเอกสารทุบตีเรียกร้องเหล่านี้AND และ OR รายการ ในขณะที่มีเหตุผลคล้ายกับ&&และ||พบว่าภายในวงเล็บเหลี่ยมพวกเขาทำงานแตกต่างกัน

ตัวอย่างบางตัวอย่างอาจอธิบายได้ดีที่สุด ...

หมายเหตุ: วงเล็บเหลี่ยมเดี่ยวและคู่ ( [ ... ]และ[[ ... ]]) เป็นคำสั่งในสิทธิ์ของตนเองที่ทำการเปรียบเทียบและส่งคืนรหัสออก ifพวกเขาไม่จริงต้อง

cmda  && cmdb  || cmdc

หากcmdaออกจริงcmdbจะถูกดำเนินการ
หากcmdaออกจากเท็จcmdbจะไม่ถูกดำเนินการ แต่cmdcเป็น

cmda; cmdb  && cmdc  || cmdd

วิธีcmdaออกจากจะถูกละเว้น
หากcmdbออกจริงcmdcจะถูกดำเนินการ
หากcmdbออกจากเท็จcmdcจะไม่ถูกดำเนินการและcmddเป็น

cmda  && cmdb; cmdc

ถ้าcmdaออกจริงจะถูกดำเนินการตามcmdb cmdc
หากcmdaออกจากเท็จcmdbจะไม่ถูกดำเนินการแต่cmdcเป็น

ฮะ? ทำไมถึงถูกcmdcประหารชีวิต?
เพราะสำหรับตัวแปลเซมิโคลอน ( ;) และบรรทัดใหม่หมายถึงสิ่งเดียวกัน Bash เห็นบรรทัดของรหัสว่า ...

cmda  && cmdb
cmdc  

เพื่อให้บรรลุสิ่งที่คาดว่าเราจะต้องใส่cmdb; cmdcภายในวงเล็บปีกกาที่จะทำให้พวกเขาเป็นสารประกอบสั่ง (กลุ่มคำสั่ง) เครื่องหมายอัฒภาคที่ยกเลิกเพิ่มเติมนั้นเป็นเพียงข้อกำหนดของ{ ...; }ไวยากรณ์ ดังนั้นเราจะได้รับ ...

cmda && { cmdb; cmdc; }
ถ้าcmdaออกจริงจะถูกดำเนินการตามcmdb cmdc
หากcmdaออกจากเท็จไม่ดำเนินการcmdbหรือcmdcถูกดำเนินการ
การดำเนินการต่อกับบรรทัดถัดไป

การใช้

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

นี่คือตัวอย่างจากรหัสที่ใช้ ...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.