ทำไมต้องตรวจสอบว่าไฟล์นั้นมีอยู่จริงก่อนที่จะทำการจัดหา?


13

เมื่อพยายามแหล่งที่มาของไฟล์คุณจะไม่ต้องการให้เกิดข้อผิดพลาดในการบอกว่าไฟล์ไม่มีอยู่เพื่อให้คุณรู้ว่าจะแก้ไขอย่างไร

ตัวอย่างเช่นnvmแนะนำให้เพิ่มสิ่งนี้ลงในโปรไฟล์ / rc ของคุณ:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

หากnvm.shไม่มีตัวเลือกข้างต้นคุณจะได้รับ "ข้อผิดพลาดเงียบ" แต่ถ้าคุณลองเอาท์พุทจะเป็น. "$NVM_DIR/nvm.sh"FILE_PATH: No such file or directory


3
คุณไม่ควร มันมีชีวิตชีวา ลองแหล่งที่มาแล้วจัดการกับข้อผิดพลาดถ้ามี
มิเคล

2
@Mikel ถูกต้อง! แล้วทำไมฉันถึงเห็นมันทุกที่
JBallin

3
@FaheemMitha ถ้าสิ่งที่คุณทำคือความปลอดภัย (เช่นโปรแกรมของคุณทำงานในนามของคนอื่น) ดังนั้นคุณจำเป็นต้องใส่ใจเกี่ยวกับสภาพการแข่งขัน ( TOCTOU ) จริงๆ ที่อาจไม่กรณีที่นี่ HOMEแต่เนื่องจากคุณมีปัญหาใหญ่ถ้ามีคนได้รับการปรับเปลี่ยนไฟล์ใน
ilkkachu

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

1
@SimonRichter ในกรณีนี้ nvm จะไม่ทำงาน ผู้ใช้จะต้องคิดออกว่าไฟล์หายไปด้วยตนเอง
JBallin

คำตอบ:


25

ใน POSIX เชลล์.เป็นบิวด์อินพิเศษดังนั้นความล้มเหลวทำให้เชลล์ออก (ในบางเชลล์เช่นbashมันทำเฉพาะเมื่ออยู่ในโหมด POSIX)

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

ทำที่นี่:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

เป็นกรณีที่คุณต้องการแหล่งที่มาของไฟล์ถ้ามันมีและไม่ถ้ามันไม่ได้ (หรือว่างเปล่าที่นี่ด้วย-s)

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

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

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

ในทางกลับกัน,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

โดยที่command¹ลบคุณลักษณะพิเศษไปยัง.คำสั่ง (ดังนั้นจึงไม่ออกจากเชลล์ด้วยข้อผิดพลาด) จะไม่ทำงานเป็น:

  • มันจะซ่อน.ข้อผิดพลาด แต่ยังข้อผิดพลาดของคำสั่งทำงานในไฟล์ที่มา
  • มันจะซ่อนเงื่อนไขข้อผิดพลาดจริงเช่นไฟล์ที่มีสิทธิ์ผิด

ไวยากรณ์ทั่วไปอื่น ๆ (ดูอินสแตนซ์ของgrep -r /etc/default /etc/init*ระบบ Debian สำหรับสคริปต์ init ที่ยังไม่ได้แปลงเป็นsystemd(ซึ่งEnvironmentFile=-/etc/default/serviceจะใช้เพื่อระบุไฟล์สภาพแวดล้อมเพิ่มเติม)) รวมถึง:

  • [ -e "$file" ] && . "$file"

    ตรวจสอบไฟล์ที่มีอยู่ยังคงแหล่งที่มาถ้ามันว่างเปล่า ยังคงมีข้อผิดพลาดร้ายแรงหากไม่สามารถเปิดได้ (แม้ว่าจะอยู่ที่นั่นหรืออยู่ที่นั่น) คุณอาจเห็นตัวแปรเพิ่มเติมเช่น[ -f "$file" ](มีอยู่และเป็นไฟล์ปกติ ), [ -r "$file" ](อ่านได้) หรือรวมกัน

  • [ ! -e "$file" ] || . "$file"

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

  • command . "$file"

    คาดว่าไฟล์จะอยู่ที่นั่น แต่จะไม่ออกหากไม่สามารถตีความได้

  • [ ! -e "$file" ] || command . "$file"

    การรวมกันของด้านบน: มันก็โอเคถ้าไฟล์ไม่อยู่ที่นั่นและสำหรับ POSIX shells, ความล้มเหลวในการเปิด (หรือแยกวิเคราะห์) ไฟล์ถูกรายงาน แต่ไม่ร้ายแรง (ซึ่งอาจเป็นที่ต้องการมากกว่า~/.profile)


¹หมายเหตุ: zshอย่างไรก็ตามคุณไม่สามารถใช้commandเช่นนั้นได้เว้นแต่จะshจำลอง โปรดทราบว่าในเปลือก Korn sourceเป็นนามแฝงcommand .ที่ไม่ใช่ตัวแปรพิเศษของ.


! ที่น่าสนใจ ฉันไม่รู้เกี่ยวกับ POSIX sh .bash_profileแต่คำถามที่เกี่ยวกับ ฉันเดาว่าปลอดภัยดีกว่าขออภัย แต่ทุบตีในโหมด POSIX เมื่อ.bash_profileมีที่มา?
Mikel

(ฉันรู้ว่าคุณสามารถตีความคำถามนี้ได้ว่าใช้กับ POSIX เชลล์ทั้งหมดได้มากขึ้นโดยอิงจากการอ่านลิงก์ nvm source ในคำถาม)
มิเคล

@Mikel คำตอบของฉันยังคงใช้bashเมื่อไม่อยู่ในโหมด POSIX คุณต้องการ[ -e /file ] && . /fileถ้าคุณไม่คิดว่ามันจะเป็นข้อผิดพลาดเมื่อไม่มีไฟล์ แหล่งที่มาลองแล้วจัดการกับข้อผิดพลาดในกรณีใด ๆไม่สามารถทำได้ที่นี่
Stéphane Chazelas

1
@Mikel นั่นเป็นสิ่งที่ต่อต้าน 1) ที่ไม่ได้ป้องกันการออกเมื่อเกิดข้อผิดพลาดกับ POSIX เชลล์ (หรือทุบตีในโหมด POSIX) 2), ที่ซ้ำกันข้อผิดพลาด (คุณใน stdout) .จะรายงานข้อผิดพลาด (บน stderr) และหากเจตนาที่จะไม่พิจารณาว่าเป็นข้อผิดพลาดเมื่อไม่มีไฟล์แสดงว่าไม่ถูกต้อง (และเป็นไปไม่ได้จากสถานะออกเพื่อบอกว่า.ล้มเหลวเนื่องจากไฟล์ไม่มีอยู่หรือไม่สามารถอ่านได้หรือเป็น ไม่สามารถแยกวิเคราะห์หรือคำสั่งสุดท้ายล้มเหลว) ซึ่งเป็นจุดที่ฉันทำในคำตอบนี้
Stéphane Chazelas

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

5

ผู้ดูแลการnvmตอบสนองของ:

ง่ายต่อการถอนการติดตั้ง nvm เพียงแค่ลบไฟล์ การบังคับให้ทำงานเพิ่มเติม (เพื่อติดตามตำแหน่งที่บรรทัดนั้นเป็นแหล่งที่มา nvm) ดูเหมือนจะไม่มีคุณค่าอย่างยิ่ง

การตีความของฉัน (รวมกับคำอธิบายที่ดีเยี่ยมของStéphaneและความคิดเห็นของ Kusalananda):

ง่ายและปลอดภัยกว่า

มันปกป้องจาก POSIX เชลล์ที่ออกเมื่อเริ่มต้นเนื่องจากไฟล์ขาดหายไป (ด้วยเหตุผลต่าง ๆ ) ผู้ที่ใช้เชลล์ที่ไม่ใช่ POSIX (เช่น bash) สามารถลบเงื่อนไขได้หากต้องการ


1
ฉันคัดค้านการตีความของคุณว่า "มุ่งเป้าไปที่ผู้เริ่มต้น" มันป้องกัน คุณไม่ต้องการให้เชลล์ล็อกอินของผู้ใช้สิ้นสุดโดยไม่คาดคิดเมื่อเริ่มต้นเพียงเพราะไฟล์นั้นหายไป ( ไม่ว่าจะด้วยเหตุผลใดก็ตาม ) หากบรรทัดของรหัสนั้นอยู่ในหนึ่งในไฟล์เชลล์ init ภายใต้/etcจะอนุญาตให้ผู้ใช้บางคนที่มีไฟล์และบางคนไม่มีไฟล์ IMHO การnvmตอบสนองของผู้ดูแลคือการสัมผัสเพียงด้านเดียวเท่านั้น
Kusalananda

1

ในขณะที่JBallinและStéphane Chazelasได้ชี้ให้เห็นใน POSIX shells การจัดหาไฟล์ที่ไม่มีอยู่จะทำให้การเข้าสู่ระบบล้มเหลว

แต่การเพิ่มการทดสอบเพื่อดูว่าไฟล์นั้นมีอยู่หรือไม่แล้วลองหาแหล่งที่มามันอาจทำให้บางสิ่งที่เรียกว่าสภาวะการแย่งชิง หากมีการเปลี่ยนแปลงบางอย่างnvm.shระหว่าง[ -s nvm.sh ]และถึง. nvm.shจะทำให้เกิดข้อผิดพลาดที่พวกเขากำลังพยายามที่จะป้องกันแม้ว่าจะมีน้อยมาก

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

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

ปรากฎว่าสิ่งนี้ไม่ทำงานใน POSIX เชลล์เพราะดังที่กล่าวมาแล้ว.ความล้มเหลวจะทำให้เชลล์ออกจากทันทีก่อนที่การจัดการข้อผิดพลาดใด ๆ จะสามารถทำงานได้

คำตอบของฉันระบุว่าเชลล์ POSIX ไม่เกี่ยวข้องกับคำถามนี้เพราะ.bash_profileไม่ควรทำงานในโหมด POSIX ดังนั้นเราสามารถทำโค้ดข้างต้นได้

จะเป็นที่ปลอดภัยที่สุดที่เราจะได้มั่นใจว่าโหมด POSIX ไม่ได้อยู่ในผลหรือให้มั่นใจโหมด POSIX ถูกปิดใช้งานโดยใช้เทคนิคที่อธิบายไว้ใน/unix//a/383581/3169

คำตอบของStéphaneมีคำแนะนำที่เป็นประโยชน์เกี่ยวกับวิธีจัดการกับ POSIX shells ทั้งหมดซึ่งฉันคิดว่าเป็นเจตนาของผู้เขียน nvm แต่แตกต่างอย่างละเอียดกว่าคำถามที่ถามที่นี่ซึ่งเป็นสาเหตุที่เรามีแนวทางที่เป็นไปได้หลายอย่างขึ้นอยู่กับเป้าหมายของคุณ .

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