ผลกระทบด้านความปลอดภัยของการลืมอ้างตัวแปรในเชลล์ bash / POSIX


206

หากคุณได้ติดตาม unix.stackexchange.com มาระยะหนึ่งแล้วคุณควรจะรู้ว่าตอนนี้การปล่อยตัวแปรที่ไม่ได้ระบุไว้ในบริบทรายการ (ดังในecho $var) ใน Bourne / POSIX shells (zsh เป็นข้อยกเว้น) มีความหมายพิเศษมากและ ไม่ควรทำจนกว่าคุณจะมีเหตุผลที่ดี

มันกล่าวถึงในระยะในจำนวนของ Q & A นี่ (ตัวอย่าง: ทำไมไม่เชลล์สคริปต์สำลักของฉันในช่องว่างหรืออักขระพิเศษอื่น ๆ , เมื่อสองครั้ง quoting จำเป็น? , การขยายตัวของตัวแปรเปลือกและผลของ glob และแยกกับมัน , ที่ยกมา vs การขยายสตริงที่ไม่ได้กล่าวถึง)

เป็นเช่นนี้มาตั้งแต่การเปิดตัว Bourne shell ในช่วงปลายยุค 70 และไม่ได้ถูกเปลี่ยนแปลงโดย Korn shell (หนึ่งในความเสียใจที่ยิ่งใหญ่ที่สุดของDavid Korn (คำถาม # 7) ) หรือbashที่คัดลอก Korn เชลล์เป็นส่วนใหญ่ วิธีที่ระบุไว้โดย POSIX / Unix

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

จากประสบการณ์ของฉันมีคน 3 ประเภทที่ละเว้นการอ้างอิงตัวแปร:

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

  • คนหลงลืม

  • คนที่ไม่เชื่อแม้หลังจากตอกซ้ำที่คิดว่าแน่นอนผู้เขียนบอร์นเชลล์ไม่ได้ตั้งใจที่เราจะพูดตัวแปรทั้งหมดของเรา

บางทีเราสามารถโน้มน้าวพวกเขาหากเราเปิดเผยความเสี่ยงที่เกี่ยวข้องกับพฤติกรรมแบบนี้

อะไรคือสิ่งที่เลวร้ายที่สุดที่อาจเกิดขึ้นได้หากคุณลืมพูดตัวแปรของคุณ มันแย่ขนาดนั้นจริงเหรอ?

เรากำลังพูดถึงจุดอ่อนแบบไหนกัน?

ในบริบทใดที่สามารถเป็นปัญหาได้


8
BashPitfallsเป็นสิ่งที่คุณคิดว่าฉันคิด
pawel7318

ลิงก์ย้อนกลับจากบทความนี้ฉันเขียนขอบคุณสำหรับการเขียน
mirabilos

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

คำตอบ:


201

คำนำ

ก่อนอื่นฉันจะบอกว่ามันไม่ใช่วิธีที่ถูกต้องในการแก้ไขปัญหา มันเหมือนกับว่า " คุณไม่ควรฆ่าคนเพราะไม่เช่นนั้นคุณจะต้องติดคุก "

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

บทสรุปเล็กน้อยสำหรับผู้ที่เพิ่งกระโดดขึ้นรถไฟ

ในเชลล์ส่วนใหญ่การปล่อยตัวแปรการขยายที่ไม่ได้กล่าวถึง (แม้ว่า (และส่วนที่เหลือของคำตอบนี้) ยังใช้กับการทดแทนคำสั่ง ( `...`หรือ$(...)) และการขยายเลขคณิต ( $((...))หรือ$[...]) มีความหมายพิเศษมาก วิธีที่ดีที่สุดที่จะอธิบายมันก็คือว่ามันเป็นเหมือนการเรียกการเรียงลำดับของนัยบางแยก + glob operator¹

cmd $var

ในภาษาอื่นจะเขียนเหมือน:

cmd(glob(split($var)))

$varแบ่งออกเป็นรายการแรกของคำตามกฎซับซ้อนที่เกี่ยวข้องกับ$IFSพารามิเตอร์พิเศษ ( ส่วนแยก ) แล้วแต่ละคำที่เกิดจากการแยกนั้นถือว่าเป็นรูปแบบที่ขยายไปยังรายการของไฟล์ที่ตรงกับมัน ( ส่วนglob ) .

ตัวอย่างเช่นถ้า$varมี*.txt,/var/*.xmlและ$IFS มี,, cmdจะเรียกว่ามีจำนวนของการขัดแย้งครั้งแรกเป็นหนึ่งcmdและคนต่อไปเป็นtxt ไฟล์ในไดเรกทอรีปัจจุบันและไฟล์ในxml/var

หากคุณต้องการโทรหาcmdเพียงแค่สองข้อโต้แย้งcmd และ*.txt,/var/*.xmlคุณเขียน:

cmd "$var"

ซึ่งจะเป็นภาษาที่คุ้นเคยมากกว่าของคุณ:

cmd($var)

เราหมายถึงอะไรจากความอ่อนแอในเชลล์ ?

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

ถึงแม้ว่าข้อเท็จจริงที่ว่าไม่มีใครบอกคุณได้ว่าเชลล์สคริปต์ไม่ควรใช้กับ CGIs ของเว็บหรือว่าระบบส่วนใหญ่ไม่อนุญาตให้ใช้สคริปต์เชลล์ setuid / setgid ทุกวันนี้สิ่งหนึ่งที่ shellshock (ข้อผิดพลาดที่หาประโยชน์จากการโจมตีจากระยะไกล พาดหัวข่าวในกันยายน 2014) เปิดเผยว่าเปลือกหอยยังคงมีการใช้อย่างกว้างขวางที่พวกเขาอาจจะไม่ได้ใน CGIs ใน DHCP สคริปต์ลูกค้าเบ็ดในคำสั่ง sudoers อุทธรณ์โดย (ถ้าไม่เป็น ) คำสั่ง setuid ...

บางครั้งก็ไม่รู้ ตัวอย่างเช่นsystem('cmd $PATH_INFO') ในสคริปต์php/ perl/ pythonCGI จะเรียกใช้เชลล์เพื่อตีความบรรทัดคำสั่งนั้น (ไม่ต้องพูดถึงข้อเท็จจริงที่ว่าcmdตัวเองอาจเป็นเชลล์สคริปต์และผู้เขียนอาจไม่เคยคาดหวังว่ามันจะถูกเรียกจาก CGI)

คุณมีช่องโหว่เมื่อมีเส้นทางสำหรับการเพิ่มสิทธิ์นั่นคือเมื่อมีคน (เรียกเขาว่าผู้โจมตี ) สามารถทำสิ่งที่เขาไม่ได้ตั้งใจ

นั่นหมายความว่าผู้โจมตีให้ข้อมูลข้อมูลที่ถูกประมวลผลโดยผู้ใช้ที่ได้รับสิทธิพิเศษ / กระบวนการซึ่งทำสิ่งที่ไม่ควรทำโดยไม่ได้ตั้งใจในกรณีส่วนใหญ่เนื่องจากข้อผิดพลาด

โดยทั่วไปคุณได้มีปัญหาเมื่อรหัสรถของคุณประมวลผลข้อมูลภายใต้การควบคุมของผู้โจมตี

ตอนนี้ไม่ชัดเจนว่าข้อมูลมาจากไหนและมักจะยากที่จะบอกว่ารหัสของคุณจะได้รับการประมวลผลข้อมูลที่ไม่น่าเชื่อถือหรือไม่

เท่าที่เกี่ยวข้องกับตัวแปรในกรณีของสคริปต์ CGI มันชัดเจนมากข้อมูลคือพารามิเตอร์ CGI GET / POST และสิ่งต่าง ๆ เช่นคุกกี้เส้นทางพารามิเตอร์โฮสต์ ...

สำหรับสคริปต์ setuid (เรียกใช้ในฐานะผู้ใช้รายหนึ่งเมื่อเรียกใช้โดยผู้อื่น) นั่นคืออาร์กิวเมนต์หรือตัวแปรสภาพแวดล้อม

อีกเวกเตอร์ที่พบบ่อยมากคือชื่อไฟล์ หากคุณได้รับรายชื่อไฟล์จากไดเรกทอรีก็เป็นไปได้ว่าไฟล์ที่ได้รับการปลูกโดยมีการโจมตี

ในเรื่องนั้นถึงแม้จะพร้อมต์ของเชลล์แบบโต้ตอบคุณอาจมีช่องโหว่ได้ (เมื่อประมวลผลไฟล์ใน/tmpหรือ~/tmp เช่น)

แม้แต่ a ~/.bashrcสามารถมีความเสี่ยงได้ (ตัวอย่างเช่นbashจะตีความมันเมื่อถูกเรียกใช้sshเพื่อเรียกใช้ForcedCommand คล้ายในgitการปรับใช้เซิร์ฟเวอร์ที่มีตัวแปรบางอย่างภายใต้การควบคุมของไคลเอ็นต์)

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

ลงเพื่อธุรกิจ มันเลวร้ายแค่ไหน?

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

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

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

การเปิดเผยข้อมูล

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

การวนรอบเสร็จสิ้นเมื่ออินพุตภายนอกที่ไม่ได้รับอนุญาตหมายความว่าผู้โจมตีสามารถทำให้คุณอ่านเนื้อหาของไดเรกทอรีใด ๆ

ใน:

echo You entered: $unsanitised_external_input

ถ้า$unsanitised_external_inputมี/*แสดงว่าผู้โจมตี/สามารถดูเนื้อหาของ ไม่ใช่เรื่องใหญ่. มันจะกลายเป็นน่าสนใจมากขึ้นแม้ว่าจะมี/home/*ที่ช่วยให้คุณมีรายชื่อของผู้ใช้บนเครื่อง, /tmp/*, /home/*/.forwardสำหรับคำแนะนำในการปฏิบัติที่เป็นอันตรายอื่น ๆ/etc/rc*/*สำหรับการให้บริการที่เปิดใช้งาน ... ไม่จำเป็นต้องตั้งชื่อพวกเขาเป็นรายบุคคล ค่า/* /*/* /*/*/*...จะแสดงรายการระบบไฟล์ทั้งหมด

การปฏิเสธช่องโหว่บริการ

ทำให้กรณีก่อนหน้านี้ไกลไปหน่อยและเรามี DoS

ที่จริงแล้วตัวแปรใด ๆ ที่ไม่ได้กล่าวถึงในบริบทรายการที่มีอินพุตที่ไม่ได้รับการแก้ไขเป็นอย่างน้อยช่องโหว่ DoS

แม้แต่ผู้เชี่ยวชาญเชลล์สแครเตอร์ทั่วไปก็มักจะลืมพูดสิ่งต่าง ๆ เช่น:

#! /bin/sh -
: ${QUERYSTRING=$1}

:เป็นคำสั่ง no-op สิ่งที่อาจจะผิดไป?

นั่นหมายถึงการกำหนด$1ให้$QUERYSTRINGถ้าไม่$QUERYSTRING ได้ตั้งค่า นั่นเป็นวิธีที่รวดเร็วในการทำให้สคริปต์ CGI สามารถเรียกใช้จากบรรทัดคำสั่งได้เช่นกัน

ที่$QUERYSTRINGยังคงขยายแม้ว่าและเนื่องจากยังไม่ได้อ้างถึงตัวดำเนินการแยก + globจะถูกเรียกใช้

ตอนนี้มี globs บางอย่างที่มีราคาแพงโดยเฉพาะอย่างยิ่งที่จะขยาย สิ่ง/*/*/*/*หนึ่งนั้นไม่ดีพอเพราะมันหมายถึงการแสดงรายการไดเรกทอรีได้ถึง 4 ระดับ นอกเหนือจากกิจกรรมของดิสก์และ CPU นั่นหมายถึงการจัดเก็บเส้นทางไฟล์นับหมื่น (40k ที่นี่บนเซิร์ฟเวอร์ขนาดเล็ก VM, 10k ของไดเรกทอรีที่)

ตอนนี้/*/*/*/*/../../../../*/*/*/*หมายถึง 40k x 10k และ /*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*เพียงพอที่จะนำเครื่องที่ทรงพลังที่สุดมาคุกเข่า

ลองด้วยตัวคุณเอง (แม้ว่าจะเตรียมไว้ให้เครื่องของคุณพังหรือแฮงค์):

a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'

แน่นอนถ้ารหัสคือ:

echo $QUERYSTRING > /some/file

จากนั้นคุณสามารถเติมดิสก์

เพียงค้นหา google บนshell cgiหรือbash cgiหรือksh cgiแล้วคุณจะพบหน้าไม่กี่หน้าที่แสดงวิธีเขียน CGIs ใน shells สังเกตว่าครึ่งหนึ่งของพารามิเตอร์กระบวนการนั้นมีความเสี่ยงอย่างไร

แม้แต่ตัวของเดวิดกรก็ ยังอ่อนไหว (ดูที่การจัดการคุกกี้)

มากถึงจุดอ่อนในการใช้รหัสโดยอำเภอใจ

การใช้รหัสโดยอำเภอใจเป็นช่องโหว่ประเภทที่แย่ที่สุดเนื่องจากหากผู้โจมตีสามารถเรียกใช้คำสั่งใด ๆ ได้ไม่มีข้อ จำกัด ในสิ่งที่เขาอาจทำ

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

awk -v foo=$external_input '$2 == foo'

ที่นี่มีจุดประสงค์เพื่อกำหนดเนื้อหาของ $external_inputตัวแปรเชลล์ให้กับfoo awkตัวแปร

ขณะนี้:

$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux

คำที่สองที่เกิดจากการแยกของ$external_input ไม่ได้มอบหมายให้fooแต่ถือว่าเป็นawkรหัส (ที่นี่ที่รันคำสั่งโดยพล: uname)

นั่นเป็นปัญหาโดยเฉพาะอย่างยิ่งสำหรับคำสั่งที่สามารถรันคำสั่งอื่น ๆ ( awk, env, sed(GNU หนึ่ง) perl, find... ) โดยเฉพาะอย่างยิ่งกับสายพันธุ์ของกนู (ซึ่งยอมรับตัวเลือกหลังจากมีปากเสียง) บางครั้งคุณจะไม่สงสัยว่าคำสั่งเพื่อให้สามารถดำเนินการอื่น ๆ เช่นksh, bashหรือzsh's [หรือprintf...

for file in *; do
  [ -f $file ] || continue
  something-that-would-be-dangerous-if-$file-were-a-directory
done

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

ถ้าเราสร้างไฟล์ที่เรียกว่าx -a a[0$(uname>&2)] -gt 1ด้วยการใช้งาน ksh ทั้งหมดอย่างน้อย (ซึ่งรวมถึงsh Unices เชิงพาณิชย์ส่วนใหญ่และ BSD บางส่วน) ที่ดำเนินการuname เพราะเชลล์เหล่านั้นทำการประเมินทางคณิตศาสตร์กับตัวดำเนินการเปรียบเทียบเชิงตัวเลขของ[คำสั่ง

$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux

เช่นเดียวกันกับสำหรับชื่อไฟล์เช่นbashx -a -v a[0$(uname>&2)]

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

ทุกสิ่งสามารถทำได้ด้วยชื่อไฟล์

$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done

และคุณก็สามารถ..เขียนได้ (ซ้ำกับ GNU chmod)

สคริปต์ที่ทำการประมวลผลไฟล์โดยอัตโนมัติในพื้นที่ที่สามารถเขียนได้แบบสาธารณะ/tmpต้องเขียนอย่างระมัดระวัง

เกี่ยวกับอะไร [ $# -gt 1 ]

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

มันก็เหมือนกับการพูด เฮ้ดูเหมือนว่า$#ไม่สามารถจะเป็นเรื่องที่ผู้ประกอบการแยก + glob ขอถามเปลือกแยก + glob มัน หรือเฮ้ขอเขียนรหัสไม่ถูกต้องเพียงเพราะข้อผิดพลาดที่ไม่น่าจะได้รับผลกระทบ

ตอนนี้มันเป็นไปได้ยังไง ตกลง, $#(หรือ$!, $?หรือการแทนที่เลขคณิตใด ๆ ) อาจมีเฉพาะตัวเลข (หรือ-สำหรับบางส่วน) ดังนั้นส่วนglobจะออก เพื่อแยกส่วนที่จะทำอะไรบางอย่าง แต่ทั้งหมดที่เราต้องการคือ$IFSการมีตัวเลข (หรือ-)

ด้วยกระสุนบางตัว$IFSอาจได้รับมาจากสภาพแวดล้อม แต่ถ้าสภาพแวดล้อมไม่ปลอดภัยมันก็จบลงแล้ว

ตอนนี้ถ้าคุณเขียนฟังก์ชั่นเช่น:

my_function() {
  [ $# -eq 2 ] || return
  ...
}

หมายความว่าพฤติกรรมของฟังก์ชันของคุณขึ้นอยู่กับบริบทที่เรียกว่า หรือกล่าวอีกนัย$IFS หนึ่งกลายเป็นหนึ่งในอินพุตของมัน พูดอย่างเคร่งครัดเมื่อคุณเขียนเอกสาร API สำหรับฟังก์ชั่นของคุณควรเป็นดังนี้:

# my_function
#   inputs:
#     $1: source directory
#     $2: destination directory
#   $IFS: used to split $#, expected not to contain digits...

และการเรียกใช้รหัสของคุณต้องทำให้แน่ใจว่า$IFSไม่มีตัวเลข ทั้งหมดนั่นเป็นเพราะคุณไม่รู้สึกอยากพิมพ์ตัวละครสองตัว

ตอนนี้สำหรับ[ $# -eq 2 ]ข้อผิดพลาดที่จะกลายเป็นช่องโหว่ที่คุณจะต้องอย่างใดสำหรับค่าของ$IFSที่จะกลายเป็นภายใต้การควบคุมของผู้โจมตี เป็นไปได้ว่าปกติแล้วจะไม่เกิดขึ้นเว้นแต่ว่าผู้โจมตีจัดการเพื่อใช้ประโยชน์จากข้อผิดพลาดอื่น

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

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

n=$(($1 + 1))
if [ $# -gt 2 ]; then
  echo >&2 "Too many arguments"
  exit 1
fi

และด้วย$1ค่า(IFS=-1234567890)ที่มีการประเมินผลเลขคณิตนั้นมีผลข้างเคียงของการตั้งค่า IFS และ[ คำสั่งถัดไปล้มเหลวซึ่งหมายความว่าการตรวจสอบargs จำนวนมากเกินไปจะถูกข้าม

จะเกิดอะไรขึ้นเมื่อไม่มีการเรียกใช้ตัวดำเนินการแยก + glob

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

[[ $a = $b ]]   # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac

ไม่ได้ทดสอบว่า$aและ$bจะเหมือนกัน (ยกเว้นด้วยzsh) แต่ถ้าตรงกับรูปแบบใน$a $bและคุณจำเป็นต้องพูด$bถ้าคุณต้องการที่จะเปรียบเทียบเป็นสตริง (สิ่งเดียวกันใน"${a#$b}"หรือ"${a%$b}"หรือ"${a##*$b*}"ที่$bควรจะยกมาถ้าไม่ได้ที่จะนำมาเป็นรูปแบบ)

สิ่งที่หมายถึงคือว่า[[ $a = $b ]]อาจจะกลับมาเป็นจริงในกรณีที่$aมีความแตกต่างจาก$b(เช่นเมื่อ$aเป็นanythingและ$bเป็น*) หรืออาจจะกลับมาเป็นเท็จเมื่อพวกเขาอยู่เหมือนกัน (ตัวอย่างเช่นเมื่อทั้งสอง$aและ$bมี[a])

สามารถทำให้เกิดช่องโหว่ด้านความปลอดภัยได้หรือไม่? ใช่เช่นข้อผิดพลาดใด ๆ ที่นี่ผู้โจมตีสามารถเปลี่ยนแปลงลอจิกโค้ดของสคริปต์และ / หรือทำลายสมมติฐานที่สคริปต์ของคุณกำลังทำอยู่ ตัวอย่างเช่นด้วยรหัสเช่น:

if [[ $1 = $2 ]]; then
   echo >&2 '$1 and $2 cannot be the same or damage will incur'
   exit 1
fi

ผู้โจมตี'[a]' '[a]'สามารถหลีกเลี่ยงการตรวจสอบโดยการส่งผ่าน

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

ฉันต้องยอมรับว่าฉันเขียน:

a=$b
case $a in...

การพูดไม่เป็นอันตราย แต่ไม่จำเป็นอย่างเคร่งครัด

อย่างไรก็ตามผลกระทบด้านหนึ่งของคำพูดที่ถนัดในกรณีดังกล่าว (เช่นใน Q & A ตอบ) ก็คือว่ามันสามารถส่งข้อความที่ผิดที่จะเริ่มต้น: ว่ามันอาจจะทั้งหมดสิทธิที่จะไม่พูดตัวแปร

ตัวอย่างเช่นพวกเขาอาจจะเริ่มคิดว่าถ้าa=$bมีการตกลงแล้วexport a=$bจะเป็นเช่นเดียว (ซึ่งก็ไม่ได้อยู่ในเปลือกหอยจำนวนมากเป็นมันในการขัดแย้งกับexportคำสั่งดังนั้นในบริบทรายการ) env a=$bหรือ

เกี่ยวกับzshอะไร

zshแก้ไขข้อผิดพลาดส่วนใหญ่ของการออกแบบเหล่านั้นได้ ในzsh(อย่างน้อยเมื่อไม่ได้อยู่ในโหมดการจำลอง sh / ksh) หากคุณต้องการแยกหรือกลมหรือการจับคู่รูปแบบคุณต้องขออย่างชัดเจน: $=varเพื่อแยกและ$~varกลมหรือเนื้อหาของตัวแปรที่จะถือว่าเป็น รูปแบบ

อย่างไรก็ตามการแยก (แต่ไม่ใช่ globbing) ยังคงดำเนินการโดยปริยายเมื่อมีการทดแทนคำสั่งที่ไม่ได้อ้างอิง (เหมือนในecho $(cmd))

นอกจากนี้ยังมีผลข้างเคียงที่ไม่พึงประสงค์บางครั้งไม่ quoting ตัวแปรคือการกำจัดเปล่า zshพฤติกรรมคล้ายกับสิ่งที่คุณสามารถประสบความสำเร็จในเปลือกหอยอื่น ๆ โดยการปิด globbing ทั้งหมด (กับset -f) และแยก (กับIFS='') ยังคงอยู่ใน:

cmd $var

จะไม่มีการแบ่ง + globแต่ถ้า$varว่างเปล่าแทนที่จะรับอาร์กิวเมนต์ว่างเปล่าหนึ่งรายการcmdจะไม่ได้รับอาร์กิวเมนต์เลย

ที่สามารถทำให้เกิดข้อบกพร่อง (เช่นชัดเจน[ -n $var ]) ที่อาจทำลายความคาดหวังและข้อสันนิษฐานของสคริปต์และทำให้เกิดช่องโหว่ แต่ตอนนี้ฉันไม่สามารถสร้างตัวอย่างที่ไม่สามารถดึงมาได้ไกลเกินไป)

สิ่งที่เกี่ยวกับเมื่อคุณทำต้องแยก + globผู้ประกอบการ?

ใช่นั่นคือโดยทั่วไปเมื่อคุณไม่ต้องการให้ตัวแปรไม่อยู่ในเครื่องหมายคำพูด แต่จากนั้นคุณต้องตรวจสอบให้แน่ใจว่าคุณปรับตัวแยกและตัวดำเนินการแบบหมุนได้อย่างถูกต้องก่อนใช้งาน หากคุณต้องการเพียงแยกส่วนและไม่globส่วนหนึ่ง (ซึ่งเป็นกรณีที่ใช้เวลาส่วนใหญ่) แล้วคุณจะต้องปิดการใช้งาน globbing ( set -o noglob/ set -f) $IFSและแก้ไข มิฉะนั้นคุณจะทำให้เกิดช่องโหว่ได้เช่นกัน (เช่นตัวอย่าง CGI ของ David Korn ที่กล่าวถึงข้างต้น)

ข้อสรุป

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

นั่นเป็นหนึ่งในเหตุผลที่ว่าทำไมมันจะถือปฏิบัติไม่ดี

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

(และในกรณีที่ยังไม่ชัดเจนโปรดหลีกเลี่ยงการเขียนรหัสความปลอดภัยในเชลล์)

และโปรดอ้างอิงตัวแปรในคำตอบของคุณในเว็บไซต์นี้!


¹ในksh93และpdkshและสัญญาซื้อขายล่วงหน้า, การขยายรั้งจะดำเนินการด้วยเว้นแต่ว่า globbing ถูกปิดใช้งาน (ในกรณีของksh93รุ่นที่สูงถึง ksh93u +, แม้ว่าbraceexpandตัวเลือกถูกปิดใช้งาน)


โปรดทราบว่าด้วย[[จะต้องมีการอ้างอิง RHS เท่านั้น:if [[ $1 = "$2" ]]; then
mirabilos

2
@mirabilos ใช่ แต่ LHS ความต้องการไม่ได้ที่จะยกมาจึงไม่มีเหตุผลที่น่าสนใจที่จะไม่พูดว่ามันมี (ถ้าเราจะใช้เวลาตัดสินใจอย่างมีสติที่จะพูดโดยค่าเริ่มต้นในขณะที่มันน่าจะเป็นสิ่งที่เหมาะสมที่สุดที่จะทำ ) นอกจากนี้โปรดทราบว่า[[ $* = "$var" ]]ไม่เหมือนกัน[[ "$*" = "$var" ]]ถ้าอักขระตัวแรกของ$IFSไม่ใช่ช่องว่างด้วยbash(และmkshถ้า$IFSว่างเปล่าในกรณีนั้นฉันไม่แน่ใจว่าสิ่งใด$*เท่ากับฉันควรเพิ่มบั๊กหรือไม่))
Stéphane Chazelas

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

2
@Barmar สมมติว่าคุณหมายถึงfoo='bar; rm *'ไม่ว่ามันจะไม่อย่างไรก็ตามมันจะแสดงรายการเนื้อหาของไดเรกทอรีปัจจุบันซึ่งอาจนับเป็นการเปิดเผยข้อมูล print $fooในksh93(ที่printจะเปลี่ยนสำหรับechoที่อยู่บางส่วนของข้อบกพร่องของตน) จะมีช่องโหว่ฉีดรหัสแม้ว่า (เช่นมีfoo='-f%.0d z[0$(uname>&2)]') (ที่คุณต้องการจริงprint -r -- "$foo". echo "$foo"ยังคงไม่ถูกต้องและไม่แน่นอน ( แต่น้อยเป็นพิษ))
Stéphane Chazelas

3
ฉันไม่ใช่ผู้เชี่ยวชาญด้านการทุบตี แต่ฉันเขียนมันมานานกว่าสิบปีแล้ว ฉันใช้คำพูดมาก แต่ส่วนใหญ่เพื่อจัดการช่องว่างที่ฝังอยู่ ตอนนี้ฉันจะใช้พวกเขามากกว่านี้! มันจะดีถ้ามีใครสักคนจะขยายคำตอบนี้เพื่อให้ง่ายขึ้นเล็กน้อยในการดูดซับคะแนนที่ดีทั้งหมด ฉันได้รับมันเยอะ แต่ก็พลาดไปเหมือนกัน มันเป็นโพสต์ที่ยาวนาน แต่ฉันรู้ว่ามีอะไรอีกมากมายให้ฉันได้เรียนรู้ ขอบคุณ!
Joe

34

[แรงบันดาลใจจากคำตอบนี้โดยcas .]

แต่ถ้าเป็น ...

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

และจะเกิดอะไรขึ้นถ้าหนึ่งในค่าที่เป็นไปได้คือสตริงว่างและฉันขึ้นอยู่กับ“ การกำจัดตะกอน”? คือถ้าตัวแปรมีสตริงว่างฉันไม่ต้องการรับสตริงว่างในคำสั่งของฉัน ฉันไม่ต้องการอะไรเลย ตัวอย่างเช่น,

ถ้าsome_condition
แล้วก็
    IgnoreCase = "- ฉัน"
อื่น
    IgnoreCase = ""
Fi
                                        # โปรดทราบว่าคำพูดในคำสั่งข้างต้นไม่จำเป็นต้องเคร่งครัด 
grep $ ignorecase   other_ grep _args

ฉันพูดไม่ออก ที่จะล้มเหลวถ้าเป็นสตริงว่างgrep "$ignorecase" other_grep_args$ignorecase

การตอบสนอง:

ตามที่กล่าวไว้ในคำตอบอื่น ๆ นี้จะยังคงล้มเหลวถ้าIFSมีหรือ- iหากคุณมั่นใจว่าIFSไม่มีตัวอักษรใด ๆ ในตัวแปรของคุณ (และคุณแน่ใจว่าตัวแปรของคุณไม่มีตัวอักษรแบบกลม) นี่ก็น่าจะปลอดภัย

แต่มีวิธีการที่ปลอดภัย (แม้ว่ามันค่อนข้างน่าเกลียดและ unintuitive มาก): ${ignorecase:+"$ignorecase"}ใช้ จากข้อมูลจำเพาะ POSIX เชลล์ภาษาคำสั่งภายใต้  2.6.2 การขยายตัวพารามิเตอร์ ,

${parameter:+[word]}

    ใช้ค่าทางเลือก   หากparameterไม่มีการตั้งค่าหรือเป็นโมฆะ null จะถูกแทนที่ มิฉะนั้นการขยายตัวของword (หรือสตริงว่างถ้าwordละเว้น) จะถูกแทนที่

เคล็ดลับที่นี่เช่นมันเป็นคือการที่เราจะใช้ignorecaseเป็นparameter และเป็น"$ignorecase" wordดังนั้น${ignorecase:+"$ignorecase"}หมายความว่า

หาก$ignorecaseไม่มีการตั้งค่าหรือเป็นโมฆะ (เช่นว่างเปล่า) โมฆะ (กล่าวคือไม่มีสิ่งใดอ้างอิง ) จะถูกแทนที่ มิฉะนั้นการขยายตัวของ"$ignorecase"จะถูกแทนที่

สิ่งนี้ทำให้เราได้ว่าเราต้องการไปที่ใด: หากตัวแปรถูกตั้งค่าเป็นสตริงว่างมันจะถูก "ลบ" (นิพจน์ที่ซับซ้อนทั้งหมดนี้จะประเมินค่าเป็นอะไร - ไม่ใช่แม้แต่สตริงว่าง) และหากตัวแปรมีค่าที่ไม่ใช่ ค่าที่ว่างเปล่าเราจะได้รับค่าที่ยกมา


แต่ถ้าเป็น ...

แต่ถ้าฉันมีตัวแปรที่ฉันต้องการ / จำเป็นต้องแยกออกเป็นคำ (นี่เป็นกรณีแรกเช่นกันสคริปต์ของฉันได้ตั้งค่าตัวแปรและฉันแน่ใจว่ามันไม่มีอักขระ glob ใด ๆ แต่มันอาจมีช่องว่างและฉันต้องการให้แบ่งออกเป็นอาร์กิวเมนต์แยกที่ช่องว่าง ขอบเขต.
PS ฉันยังต้องการการกำจัดเปล่า)

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

ถ้าsome_condition
แล้วก็
    criteria = "- type f"
อื่น
    เกณฑ์ = ""
Fi
ถ้าsome_other_condition
แล้วก็
    criteria = "$ criteria -mtime +42"
Fi
ค้นหา "$ start_directory" $ criteria   other_ find _args

การตอบสนอง:

evalคุณอาจจะคิดว่าเป็นกรณีสำหรับการใช้นี้  No!   ต้านทานการล่อใจแม้กระทั่งคิดถึงการใช้evalที่นี่

อีกครั้งหากคุณมั่นใจว่าIFSไม่มีอักขระใด ๆ ในตัวแปรของคุณ (ยกเว้นช่องว่างที่คุณต้องการให้เกียรติ) และคุณมั่นใจว่าตัวแปรของคุณไม่มีอักขระ glob ใด ๆ ข้างต้นอาจเป็นไปได้ ปลอดภัย

แต่ถ้าคุณใช้ bash (หรือ ksh, zsh หรือ yash) มีวิธีที่ปลอดภัยกว่า: ใช้อาร์เรย์:

ถ้าsome_condition
แล้วก็
    criteria = (- - type f) # คุณสามารถพูดได้ `criteria = (" - type "" f ")` แต่มันไม่จำเป็นจริงๆ
อื่น
    criteria = () # อย่าใช้เครื่องหมายคำพูดใด ๆ กับคำสั่งนี้!
Fi
ถ้าsome_other_condition
แล้วก็
    criteria + = (- mtime +42) # หมายเหตุ: ไม่ใช่ `=`, แต่ ` + = ', เพื่อเพิ่ม (ผนวก) เข้าไปในอาร์เรย์
Fi
ค้นหา "$ start_directory" "$ {criteria [@]}"   other_ค้นหา_args

จากทุบตี (1) ,

องค์ประกอบของอาร์เรย์ใด ๆ อาจถูกอ้างอิงโดยใช้ ... ถ้าเป็นหรือ  คำขยายไปยังสมาชิกทุกคนของ ตัวห้อยเหล่านี้แตกต่างกันเฉพาะเมื่อคำนั้นปรากฏภายในเครื่องหมายคำพูดคู่ หากคำนี้เป็นคำที่ยกมาสองครั้ง ... ขยายองค์ประกอบของแต่ละคำเป็นคำที่แยกต่างหาก${name[subscript]}subscript@*name${name[@]}name

ดังนั้น"${criteria[@]}"ขยายไปยัง (ในตัวอย่างข้างต้น) องค์ประกอบศูนย์สองหรือสี่ของcriteriaอาร์เรย์แต่ละคนที่ยกมา โดยเฉพาะอย่างยิ่งหากไม่มีเงื่อนไข  s เป็นจริงcriteriaอาร์เรย์ไม่มีเนื้อหา (ตามที่กำหนดโดยcriteria=()คำสั่ง) และ"${criteria[@]}"ประเมินเป็นไม่มีอะไร (แม้แต่สตริงว่างที่ไม่สะดวก)


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

printf "ป้อนชื่อไฟล์เพื่อค้นหา:"
อ่าน fname
ถ้า ["$ fname"! = ""]
แล้วก็
    criteria + = (- ชื่อ "$ fname")
Fi

โปรดทราบว่า$fnameจะถูกยกมาในแต่ละครั้งที่มีการใช้ สิ่งนี้ใช้ได้แม้ว่าผู้ใช้จะป้อนสิ่งที่ชอบfoo barหรือfoo*"${criteria[@]}"ประเมินหรือ-name "foo bar" -name "foo*"(โปรดจำไว้ว่าแต่ละองค์ประกอบของอาเรย์นั้นมีการเสนอราคา)

อาร์เรย์ไม่ทำงานในเชลล์ POSIX ทั้งหมด อาร์เรย์คือ ksh / bash / zsh / yash-ism ยกเว้น ... มีหนึ่งอาร์เรย์ที่เปลือกหอยสนับสนุน: "$@"รายการอาร์กิวเมนต์อาคา หากคุณทำรายการอาร์กิวเมนต์ที่คุณเรียกใช้ด้วย (เช่นคุณได้คัดลอก“ พารามิเตอร์ตำแหน่ง” ทั้งหมด (อาร์กิวเมนต์) ลงในตัวแปรหรือดำเนินการกับตัวแปรเหล่านั้น) คุณสามารถใช้รายการอาร์กิวเมนต์เป็นอาร์เรย์:

ถ้าsome_condition
แล้วก็
    set - -type f # คุณสามารถพูด `set -" -type "" f "" แต่มันไม่จำเป็นจริงๆ
อื่น
    ชุด -
Fi
ถ้าsome_other_condition
แล้วก็
    set - "$ @" - เวลา +42
Fi
# ทำนองเดียวกัน: set - "$ @" -name "$ fname"
ค้นหา "$ start_directory" "$ @"   other_ค้นหา_args

"$@"สร้าง (ซึ่งในอดีตมาก่อน) มีความหมายเช่นเดียวกับ- มันขยายแต่ละอาร์กิวเมนต์ (เช่นองค์ประกอบแต่ละรายการอาร์กิวเมนต์) เพื่อแยกคำเช่นถ้าคุณได้พิมพ์"${name[@]}""$1" "$2" "$3" …

excerpting จากสเปค POSIX เชลล์ภาษาคำสั่งภายใต้2.5.2 พารามิเตอร์พิเศษ ,

@

    ขยายไปยังพารามิเตอร์ตำแหน่งเริ่มต้นจากหนึ่งเริ่มต้นผลิตหนึ่งฟิลด์สำหรับแต่ละพารามิเตอร์ตำแหน่งที่ถูกตั้งค่า …, เขตข้อมูลเริ่มต้นจะถูกเก็บไว้เป็นเขตข้อมูลแยก…. หากไม่มีพารามิเตอร์ตำแหน่งการขยายตัวของ@จะสร้างเขตข้อมูลเป็นศูนย์แม้เมื่อ@อยู่ในเครื่องหมายคำพูดคู่; ...

ข้อความทั้งหมดค่อนข้างคลุมเครือ จุดสำคัญคือมันระบุว่า"$@"จะสร้างเขตข้อมูลเป็นศูนย์เมื่อไม่มีพารามิเตอร์ตำแหน่ง บันทึกทางประวัติศาสตร์: เมื่อ"$@"เปิดตัวครั้งแรกในเชลล์เป้าหมาย (รุ่นก่อนถึงการทุบตี) ในปี 1979 มันมีข้อผิดพลาดที่"$@"ถูกแทนที่ด้วยสตริงว่างเดียวเมื่อไม่มีพารามิเตอร์ตำแหน่ง เห็นอะไร${1+"$@"}หมายถึงในสคริปต์เปลือกและวิธีการที่ไม่ได้แตกต่างจาก"$@"? แบบดั้งเดิมบอร์นเชลล์ครอบครัวอะไร${1+"$@"}หมายถึง ... และที่มันเป็นสิ่งที่จำเป็น? และเมื่อเทียบกับ"$@"${1+"$@"}


ช่วยจัดเรียงสถานการณ์แรกด้วย:

ถ้าsome_condition
แล้วก็
    ignorecase = (- i) # คุณสามารถพูดว่า `ignorecase = (" - i ")` แต่มันไม่จำเป็นจริงๆ
อื่น
    ignorecase = () # อย่าใช้เครื่องหมายคำพูดใด ๆ กับคำสั่งนี้!
Fi
grep "$ {ignorecase [@]}"   other_ grep _args

____________________

PS (csh)

สิ่งนี้ควรดำเนินการโดยไม่บอก แต่เพื่อประโยชน์ของผู้ที่ยังใหม่ที่นี่: csh, tcsh ฯลฯ ไม่ใช่เชลล์ Bourne / POSIX พวกเขาเป็นครอบครัวที่แตกต่างกันโดยสิ้นเชิง ม้าที่มีสีต่างกัน เกมลูกอื่น ๆ ทั้งหมด สายพันธุ์ของแมวที่แตกต่างกัน ขนนกอีกชนิดหนึ่ง และโดยเฉพาะอย่างยิ่งความสามารถต่าง ๆ ของหนอน

บางสิ่งที่ถูกกล่าวถึงในหน้านี้นำไปใช้กับ csh; เช่น: เป็นความคิดที่ดีที่จะพูดถึงตัวแปรทั้งหมดของคุณเว้นแต่คุณจะไม่มีเหตุผลที่ดีที่จะทำและคุณแน่ใจว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่ แต่ใน csh ทุกตัวแปรเป็นอาร์เรย์ - มันเกิดขึ้นที่เกือบทุกตัวแปรเป็นอาร์เรย์ขององค์ประกอบเดียวเท่านั้นและทำหน้าที่คล้ายกับตัวแปรเชลล์สามัญในเชลล์ Bourne / POSIX และไวยากรณ์แตกต่างกันมาก (และฉันหมายถึงสุดขีด ) ดังนั้นเราจะไม่พูดอะไรเพิ่มเติมเกี่ยวกับกระสุน csh-family ที่นี่


1
โปรดทราบว่าในcshคุณควรจะใช้$var:qมากกว่า"$var"ที่จะไม่ทำงานกับตัวแปรที่มีอักขระขึ้นบรรทัดใหม่ (หรือถ้าคุณต้องการอ้างอิงองค์ประกอบของแต่ละอาร์เรย์แทนที่จะรวมกับช่องว่างในอาร์กิวเมนต์เดียว)
Stéphane Chazelas

ในทุบตีbitrate="--bitrate 320"ทำงานด้วย${bitrate:+$bitrate}และbitrate=--bitrate 128จะไม่ทำงาน${bitrate:+"$bitrate"}เพราะมันทำลายคำสั่ง ปลอดภัยที่จะใช้${variable:+$variable}โดยไม่ต้อง"?
Freedo

@ เฟรโด้: ฉันพบความคิดเห็นของคุณไม่ชัดเจน ฉันไม่ชัดเจนโดยเฉพาะอย่างยิ่งสิ่งที่คุณต้องการรู้ว่าฉันยังไม่ได้พูด ดูหัวข้อที่สอง“ แต่จะเกิดอะไรขึ้นถ้า” ส่วนหัว -“ แต่ถ้าฉันมีตัวแปรที่ฉันต้องการ / จำเป็นต้องแยกเป็นคำ?” นั่นคือสถานการณ์ของคุณและคุณควรทำตามคำแนะนำที่นั่น แต่ถ้าคุณทำไม่ได้ (เช่นเนื่องจากคุณใช้เชลล์อื่นที่ไม่ใช่ bash, ksh, zsh หรือ yash และคุณใช้  $@อย่างอื่น) หรือคุณเพียงปฏิเสธที่จะใช้อาร์เรย์ด้วยเหตุผลอื่นฉันแนะนำ คุณเป็น“ ดังที่กล่าวไว้ในคำตอบอื่น ๆ ” … (ต่อ)
G-Man

(ต่อ) ... ข้อเสนอแนะของคุณ (ใช้${variable:+$variable}กับไม่มี") จะล้มเหลวถ้ามีไอเอฟเอ  -,  0, 2, 3, a, b, e, i, หรือrt
G-Man

ตัวแปรของฉันมีทั้ง a, e, b, i 2 และ 3 ตัวอักษรและยังใช้งานได้ดีด้วย${bitrate:+$bitrate}
Freedo

11

ฉันสงสัยคำตอบของStéphane แต่ก็เป็นไปได้ที่จะมีการละเมิด$#:

$ set `seq 101`

$ IFS=0

$ echo $#
1 1

หรือ $?:

$ IFS=0

$ awk 'BEGIN {exit 101}'

$ echo $?
1 1

สิ่งเหล่านี้เป็นตัวอย่างที่วางแผนไว้ แต่ศักยภาพนั้นมีอยู่จริง

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