ทุบตีถ้าไม่ได้หลายเงื่อนไขโดยไม่ต้อง subshell?


23

ฉันต้องการรวมหลายเงื่อนไขในเชลล์ถ้าคำสั่งและปฏิเสธการรวมกัน ฉันมีรหัสการทำงานต่อไปนี้สำหรับการรวมกันของเงื่อนไขง่าย ๆ :

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

มันใช้งานได้ดี ถ้าฉันต้องการที่จะปฏิเสธฉันสามารถใช้รหัสการทำงานต่อไปนี้:

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

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


ฉันคิดว่าฉันสามารถใช้ตรรกะบูลีนเพื่อกำหนดนิพจน์ที่เทียบเท่าได้: if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thenแต่ฉันต้องการคำตอบทั่วไป
Wildcard

คุณสามารถลบล้างวงเล็บปีกกาได้เช่นif [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti

1
ใช่ฉันเพิ่งทดสอบif [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fiและใช้งานได้ ฉันมักจะเขียนการทดสอบด้วยเครื่องหมายวงเล็บปีกกาเป็นเรื่องของนิสัย นอกจากนี้เพื่อให้แน่ใจว่าไม่ได้bashฉันทำการทดสอบต่อไปif /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fiและใช้งานได้เช่นกัน
DopeGhoti

1
@Wildcard - [ ! -e file/. ] && [ -r file ]จะวางไดเรกทอรี ลบล้างมันตามที่คุณต้องการ แน่นอนว่าเป็นสิ่งที่-dทำ
mikeserv

1
คำถามที่เกี่ยวข้อง (คล้ายกัน แต่ไม่มีการอภิปรายและคำตอบที่ไม่ค่อยเป็นประโยชน์): unix.stackexchange.com/q/156885/135943
Wildcard

คำตอบ:


32

คุณต้องใช้{ list;}แทน(list):

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

ทั้งคู่เป็นคำสั่งการจัดกลุ่มแต่{ list;}เรียกใช้งานคำสั่งในสภาพแวดล้อมเชลล์ปัจจุบัน

หมายเหตุนั้น;ใน{ list;}เป็นสิ่งจำเป็นเพื่อคั่นรายการจาก}คำกลับคุณสามารถใช้ตัวคั่นอื่น ๆ เช่นกัน {นอกจากนี้ยังจำเป็นต้องเว้นวรรค (หรือตัวคั่นอื่น)


ขอขอบคุณ! สิ่งที่ทำให้ฉันสะดุดในตอนแรก (เนื่องจากฉันใช้เครื่องมือจัดฟันแบบโค้งawkบ่อยกว่าbash) ความต้องการช่องว่างหลังจากช่องเปิดแบบเปิด คุณพูดถึง "คุณสามารถใช้ตัวคั่นอื่นเช่นกัน"; ตัวอย่างใด ๆ
สัญลักษณ์แทน

@ Wildcard: ใช่ช่องว่างหลังจากวงเล็บปีกกาเปิดเป็นสิ่งจำเป็น ตัวอย่างสำหรับตัวคั่นอื่นเป็นบรรทัดใหม่เนื่องจากคุณมักต้องการให้คุณกำหนดฟังก์ชัน
cuonglm

@don_crissti: ในzshคุณสามารถใช้ช่องว่าง
cuonglm

@don_crissti: &ตัวคั่นยังเป็นคุณสามารถใช้{ echo 1;:&}ขึ้นบรรทัดใหม่ได้หลายบรรทัดสามารถใช้ได้เช่นกัน
cuonglm

2
คุณสามารถใช้เครื่องหมายอัญประกาศเดี่ยวจากคู่มือthey must be separated from list by whitespace or another shell metacharacter.ในการทุบตีพวกเขาคือ: metacharacter: | & ; ( ) < > space tab . สำหรับงานนี้โดยเฉพาะฉันเชื่อว่า& ; ) space tabจะทำงานใด ๆ .... .... จาก zsh (ตามความคิดเห็น) each sublist is terminated by &', &!', or a newline.

8

คุณสามารถใช้testฟังก์ชันทั้งหมดเพื่อให้ได้ตามที่คุณต้องการ จากหน้าคนของ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.

ดังนั้นสภาพของคุณอาจมีลักษณะ:

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

สำหรับการปฏิเสธการใช้วงเล็บหนี:

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ปฏิเสธเงื่อนไขที่ซับซ้อนในเปลือกคุณอาจต้องใช้กฎหมาย De ของมอร์แกนและผลักดันการปฏิเสธตลอดทางลงภายใน[สาย ...

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

... หรือคุณต้องใช้then :; else...

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

if ! commandไม่สามารถใช้ได้ portably [[และไม่เป็น

ถ้าคุณไม่จำเป็นต้องพกพารวมไม่ได้เขียนสคริปต์เปลือก คุณจริงมากขึ้นแนวโน้มที่จะพบในระบบปฏิบัติการยูนิกซ์สุ่มเลือกกว่าที่เป็นอยู่/usr/bin/perlbash


1
!เป็น POSIX ซึ่งทุกวันนี้พกพาได้เพียงพอ แม้แต่ระบบที่ยังมาพร้อมกับ Bourne shell ยังมี POSIX sh ที่อื่นบนระบบไฟล์ซึ่งคุณสามารถใช้เพื่อตีความไวยากรณ์มาตรฐานของคุณ
Stéphane Chazelas

@ StéphaneChazelasนี้เป็นจริงในทางเทคนิค แต่ในประสบการณ์ของผมมันเป็นเรื่องง่ายที่จะเขียนสคริปต์ใน Perl กว่าก็คือการจัดการกับความยุ่งยากของการตั้งและเปิดใช้งานสภาพแวดล้อมเปลือก POSIX /bin/shสอดคล้องเริ่มต้นจาก สคริปต์ Autoconf ทำตามที่คุณแนะนำ แต่ฉันคิดว่าเราทุกคนรู้ว่าการรับรองนั้นน้อยแค่ไหน
zwol

5

คนอื่นสังเกตการจัดกลุ่ม{คำสั่งผสม;}แต่ถ้าคุณทำการทดสอบที่เหมือนกันในชุดคุณอาจต้องการใช้ชนิดอื่น:

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

... ตามที่แสดงในที่อื่น{ :;}ไม่มีความยากลำบากในการใช้คำสั่งผสมซ้อนกัน ...

โปรดทราบว่าการทดสอบข้างต้น(โดยทั่วไป)สำหรับไฟล์ปกติ หากคุณกำลังมองเฉพาะที่มีอยู่ , อ่านไฟล์ที่ไม่ได้ไดเรกทอรี:

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

หากคุณไม่สนใจว่าพวกเขาเป็นไดเรกทอรีหรือไม่:

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

... งานสำหรับการใด ๆที่สามารถอ่านได้ , สามารถเข้าถึงไฟล์ แต่มีแนวโน้มที่จะแขวน FIFOs w / out นักเขียน


2

นี่ไม่ใช่คำตอบแบบเต็มของคำถามหลักของคุณ แต่ฉันสังเกตเห็นว่าคุณพูดถึงการทดสอบแบบผสม (ไฟล์ที่อ่านได้) ในความคิดเห็น เช่น,

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

คุณสามารถรวมสิ่งนี้ได้ด้วยการกำหนดฟังก์ชั่นเชลล์; เช่น,

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

เพิ่มการจัดการข้อผิดพลาดใน (เช่น[ $# = 1 ]) เพื่อลิ้มรส ครั้งแรกifคำสั่งข้างต้นสามารถรวมกับ

if readable_file file1  &&  readable_file file2  &&  readable_file file3

และคุณสามารถย่อให้สั้นลงได้อีกโดยย่อชื่อฟังก์ชัน ในทำนองเดียวกันคุณสามารถกำหนดnot_readable_file()(หรือnrfสั้น ๆ ) และรวมถึงการปฏิเสธในฟังก์ชั่น


ดีมาก แต่ทำไมไม่เพิ่มเพียงห่วง readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (ข้อจำกัดความรับผิดชอบ: ฉันทดสอบโค้ดนี้และใช้งานได้ แต่ไม่ใช่แบบสายการบินเดียวฉันเพิ่มเซมิโคลอนสำหรับการโพสต์ที่นี่ แต่ไม่ได้ทดสอบตำแหน่งของพวกเขา)
Wildcard

1
จุดดี. ฉันจะเพิ่มความกังวลเล็กน้อยว่ามันซ่อนตรรกะการควบคุม (ร่วม) ในฟังก์ชัน; ใครสักคนที่อ่านสคริปต์และเห็นif readable_files file1 file2 file3จะไม่ทราบว่าฟังก์ชั่นได้ทำและหรือOR - แม้ว่า (ก) ผมคิดว่ามันง่ายอย่างเป็นธรรม (ข) ถ้าคุณไม่ได้กระจายสคริปต์ก็พอที่ดีถ้าคุณเข้าใจมัน และ (c) เนื่องจากฉันแนะนำให้ย่อชื่อฟังก์ชันไปสู่ความสับสนฉันจึงไม่สามารถพูดคุยเกี่ยวกับการอ่านได้ … (ต่อ)
G-Man พูดว่า 'Reinstate Monica'

1
(ต่อ) … BTW ฟังก์ชั่นนั้นดีในแบบที่คุณเขียน แต่คุณสามารถแทนที่for file in "$@" ; doด้วยfor file do- มันเป็นค่าเริ่มต้นin "$@"และ (ค่อนข้างตอบโต้อย่างสังหรณ์) ;ไม่เพียง แต่ไม่จำเป็นเท่านั้น
G-Man กล่าวว่า 'Reinstate Monica'

0

คุณสามารถคัดค้านภายในการทดสอบรั้งได้ดังนั้นเพื่อนำรหัสเดิมของคุณกลับมาใช้ใหม่:

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

2
คุณต้องการ||แทน&&
cuonglm

การทดสอบไม่ "เป็นไฟล์ใด ๆที่ไม่มี" การทดสอบคือ " ไม่มีไฟล์ใด ๆ " การใช้||จะดำเนินการวินิจฉัยถ้าใด ๆของไฟล์ที่ไม่ได้อยู่ไม่ได้ถ้าและเพียง IFF ทั้งหมดของไฟล์ที่ไม่ได้อยู่ ไม่ (A และ B และ C) เหมือนกับ (ไม่ใช่ A) และ (ไม่ใช่ B) และ (ไม่ใช่ C); ดูคำถามเดิม
DopeGhoti

@DopeGhoti, cuonglm ถูกต้อง มันหากไม่ได้ (ไฟล์ทั้งหมดที่มี) ดังนั้นเมื่อคุณแยกมันขึ้นด้วยวิธีนี้คุณจะต้องแทน|| &&ดูความคิดเห็นแรกของฉันด้านล่างคำถามของฉันเอง
Wildcard

2
@DopeGhoti: เป็นเช่นเดียวกับnot (a and b) (not a) or (not b)ดูen.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm

1
มันจะต้องเป็นวันพฤหัสบดี ฉันไม่สามารถหยุดวันพฤหัสบดีได้ ถูกต้องแล้วฉันจะทิ้งเท้าไว้ที่ลูกหลาน
DopeGhoti

-1

ฉันต้องการใช้พวกมันสำหรับการจัดกลุ่ม แต่จริงๆแล้วมันวางไข่กับเชลล์หรือไม่ (ฉันจะบอกได้อย่างไร)

ใช่คำสั่งภายใน (...)จะทำงานใน subshell

ในการทดสอบว่าคุณอยู่ใน subshell หรือไม่คุณสามารถเปรียบเทียบ PID ของเชลล์ปัจจุบันกับ PID ของเชลล์เชิงโต้ตอบ

echo $BASHPID; (echo $BASHPID); 

ตัวอย่างเอาท์พุทแสดงให้เห็นว่าวงเล็บวางไข่ subshell และวงเล็บปีกกาไม่ได้:

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

6
()วางไข่ subshell {}
.. บางที

@heemayl ที่ส่วนอื่น ๆ ของฉันคำถามจะมีวิธีที่ฉันสามารถดูหรือแสดงให้เห็นบนหน้าจอของฉันเป็นจริงของการ subshell ที่ถูกกลับกลาย? (ที่จริงอาจจะเป็นคำถามที่แยกต่างหาก แต่จะดีที่จะรู้ว่าที่นี่.)
Wildcard

1
@heemayl คุณพูดถูก! ฉันได้ทำการecho $$ && ( echo $$ )เปรียบเทียบ PID แล้วและมันก็เหมือนเดิม แต่ก่อนที่ฉันจะส่งความคิดเห็นที่บอกคุณว่าคุณคิดผิดฉันได้ลองทำอย่างอื่นแล้ว echo $BASHPID && ( echo $BASHPID )ให้ PID ที่แตกต่างกัน
David King

1
@ heemayl echo $BASHPID && { echo $BASHPID; }ให้ PID เดียวกับที่คุณแนะนำไว้ในกรณีนี้ ขอบคุณสำหรับการศึกษา
David King

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