วิธีการกำหนดเชลล์สคริปต์ที่จะต้องจัดหาแหล่งที่มาไม่ได้ทำงาน


40

ฉันกำลังกำหนดเชลล์สคริปต์ซึ่งผู้ใช้ควรsourceจะดำเนินการมากกว่า

มีวิธีธรรมดาหรือชาญฉลาดในการบอกกล่าวผู้ใช้ว่าเป็นเช่นนี้ผ่านทางนามสกุลไฟล์หรือไม่?

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


1
ดังนั้นหากผู้ใช้กำลังเขียนเชลล์สคริปต์หนึ่งบรรทัดxซึ่งเพิ่งมีคำสั่ง. your-script-to-be-sourcedมันก็โอเค แต่ถ้าเขาต้องการที่จะรันbash your-script-to-be-sourcedมันควรจะถูกห้าม? ประเด็นของข้อ จำกัด นี้คืออะไร?
user1934428

8
@ user1934428 แน่นอน นี่เป็นเรื่องปกติสำหรับสคริปต์ที่คำนวณจำนวนenvตัวแปรและปล่อยให้สิ่งเหล่านี้เป็นผลลัพธ์ของสคริปต์โดยพฤตินัย สามเณรจะติดอยู่กับปริศนาเป็นเวลาหลายวันหากคุณอนุญาต
kubanczyk

คำตอบ:


45

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

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

ภายใต้ bash ${BASH_SOURCE[0]}จะมีชื่อของไฟล์ปัจจุบันที่เชลล์อ่านอยู่ไม่ว่าจะมีแหล่งที่มาหรือดำเนินการอยู่

ในทางตรงกันข้าม$0ชื่อของไฟล์ปัจจุบันที่ถูกเรียกใช้งาน

-efทดสอบว่าไฟล์ทั้งสองนี้เป็นไฟล์เดียวกันหรือไม่ หากเป็นเช่นนั้นเราจะเตือนผู้ใช้และออก

ทั้ง-efมิได้BASH_SOURCEมี POSIX ในขณะที่-efรองรับโดย ksh, yash, zsh และ Dash BASH_SOURCEต้องใช้ bash ในzshแต่จะถูกแทนที่ด้วย${BASH_SOURCE[0]}${(%):-%N}


2
เพียงแค่ echo "Usage: source \"$myfile\""
kubanczyk

6
@kancanczyk sourceไม่สามารถพกพาได้ พิจารณาว่าคำตอบนี้เป็นทุบตีเฉพาะก็ไม่ได้ว่าไม่ดี แต่มันเป็นนิสัยที่ดีที่จะใช้แบบพกพา.
gronostaj

33

ไฟล์ที่ไม่สามารถเรียกใช้งานได้สามารถหาแหล่งที่มาได้ แต่ไม่สามารถดำเนินการได้ดังนั้นในฐานะบรรทัดแรกของการป้องกัน

แก้ไข: เคล็ดลับที่ฉันเพิ่งพบ: ทำให้ shebang เป็นไฟล์ปฏิบัติการที่ไม่ใช่ตัวแปลเชลล์/bin/falseทำให้สคริปต์ส่งคืนข้อผิดพลาด (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"

7
อย่างไรก็ตามไฟล์ที่ไม่สามารถดำเนินการได้นั้นยังคงสามารถดำเนินการได้ผ่านทางbash somefile.sh...
twalberg

1
ถ้าคุณรู้ว่าคุณสามารถดำเนินการได้ด้วยbash(VD Perl, หลาม awk ... ) แล้วคุณได้ดูทีแหล่งที่มาและเห็นความคิดเห็นที่ระบุว่าจะไม่ทำมัน :)
xenoid

1
โดยทั่วไปแล้วสคริปต์ Perl จะตั้งชื่อsomefile.plและ Python เป็นsomefile.pyดังนั้นฉันอาจไม่ได้อ่านความคิดเห็น (อะไรคือสิ่งนั้นถึงกระนั้นหรือไม่) และbash somefile.shสั้นกว่าที่จะพิมพ์กว่าchmod +x somefile.sh; ./somefile.sh...
twalberg

นอกจากนี้เชลล์ที่คล้ายบอร์นบางตัวรวมถึงbashจะลองexecveไฟล์เป็นอันดับแรกแต่หากล้มเหลวพวกเขาตรวจสอบไฟล์ด้วยตนเองและตีความด้วยตนเอง#!และเรียกใช้ผ่านล่ามนั้น: นี่เป็นมรดกจากสมัยที่ผู้#!ใช้หมดจด ระเบียบปฏิบัติแทนที่จะถูกจัดการโดยเคอร์เนลเอง อย่างน้อยฉันก็คิดว่า bashจะไม่ทำสิ่งนี้สำหรับไฟล์ที่ไม่สามารถเรียกใช้งานได้ แต่ฉันไม่รู้ว่ามันเป็นอุปกรณ์พกพาที่จะคาดหวังพฤติกรรมที่มีสติเช่นนี้จากเชลล์ทั้งหมดที่ผู้ใช้อาจเรียกใช้สคริปต์จาก
mtraceur

"ถ้าคุณรู้ว่าคุณสามารถรันมันได้ด้วยการทุบตี" อืมไม่บางครั้งผู้ใช้ก็ไม่ทราบว่ามีกระสุนอื่นอยู่มากกว่าbash และbash script.shอาจเป็นอันตรายได้
Sergiy Kolodyazhnyy

10

มีหลายวิธีที่แนะนำในโพสต์สแต็คโอเวอร์โฟลว์ซึ่งฉันชอบฟังก์ชั่นที่แนะนำโดยWirawan Purwantoและmr.spuraticที่สุด:

วิธีที่แข็งแกร่งที่สุดตามที่ Wirawan Purwanto แนะนำไว้คือการตรวจสอบ FUNCNAME[1] ภายในฟังก์ชั่น :

function mycheck() { declare -p FUNCNAME; }
mycheck

แล้ว:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

นี่คือเทียบเท่ากับการตรวจสอบการส่งออกของcallerค่า mainและsourceแยกความแตกต่างบริบทของผู้โทร การใช้ FUNCNAME[]ช่วยให้คุณบันทึกและแยกวิเคราะห์callerเอาต์พุต คุณจำเป็นต้องรู้หรือคำนวณความลึกของการโทรเพื่อให้ถูกต้อง กรณีเช่นสคริปต์ที่มีที่มาจากภายในฟังก์ชั่นหรือสคริปต์อื่นจะทำให้อาร์เรย์ (สแต็ค) จะลึก ( FUNCNAMEเป็นตัวแปรอาเรย์ bash พิเศษควรมีดัชนีต่อเนื่องที่สอดคล้องกับ call stack ตราบใดที่ไม่เคยมีunset)

ดังนั้นคุณสามารถเพิ่มไปยังจุดเริ่มต้นของสคริปต์:

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check

7

สมมติว่ามันไร้ประโยชน์แทนที่จะเป็นอันตรายในการเรียกใช้สคริปต์คุณสามารถเพิ่ม

return 0 || printf 'Must be sourced, not executed\n' >&2

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


3
โปรดทราบว่าสิ่งนี้จะส่งคืนสถานะการออก 0 ลองใช้สำนวนที่คล้ายกันนี้ที่ฉันใช้แทน:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea

5

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

#!/bin/bash source-this-script
# ...

ข้อความแสดงข้อผิดพลาดจะเป็นดังนี้:

/bin/bash: source-this-script: No such file or directory

ชื่ออาร์กิวเมนต์ (ตามอำเภอใจ) ให้คำใบ้ไว้แล้ว แต่ข้อความแสดงข้อผิดพลาดยังไม่ชัดเจน 100% เราสามารถแก้ไขได้ด้วยสคริปต์อรรถประโยชน์source-this-scriptที่วางไว้ในที่ของคุณPATH:

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

ตอนนี้ข้อความแสดงข้อผิดพลาดจะเป็นดังนี้:

This script must be sourced, not executed: path/to/script.sh

เปรียบเทียบกับแนวทางอื่น ๆ

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

มันไม่ได้ป้องกันการร้องขออย่างชัดเจนผ่านทางbash path/to/script.sh(ขอบคุณ @muru!)


1
ข้อเสียอีกอย่างหนึ่งก็คือสิ่งนี้จะไม่ป้องกันbash some/script.shซึ่งก็จะไม่สนใจ Shebang
muru

4
คุณสามารถทำให้ข้อความชัดเจนยิ่งขึ้นด้วยการทำให้ shebang #!/bin/echo 'You must source this script!'หรืออะไรทำนองนั้น
คริส

1
@Chris: ใช่ แต่ฉันจะสูญเสียการตรวจจับประเภทไฟล์ (เช่นใน Vim) และเอกสารประกอบของเชลล์ภาษานี้ หากคุณไม่สนใจสิ่งเหล่านี้คำแนะนำของคุณจะกำจัดสคริปต์ที่สอง!
Ingo Karkat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.