วิธีการตรวจสอบว่าสคริปต์นั้นมีที่มาหรือไม่


217

ฉันมีสคริปต์ที่ฉันไม่ต้องการให้มันโทรหาexitถ้ามันมีที่มา

ฉันคิดว่าการตรวจสอบหากแต่ตอนนี้มีปัญหาหากสคริปต์ที่มีที่มาจากสคริปต์อื่นหรือถ้าแหล่งที่มาของผู้ใช้จากเปลือกแตกต่างกันเช่น $0 == bashksh

มีวิธีที่เชื่อถือได้ในการตรวจสอบว่าสคริปต์นั้นมีที่มาหรือไม่?


2
ฉันมีปัญหาที่คล้ายกันในขณะที่กลับมาและแก้ไขได้โดยหลีกเลี่ยง 'ทางออก' ในทุกกรณี; "kill -INT $$" ยุติสคริปต์อย่างปลอดภัยไม่ว่าในกรณีใด
JESii

1
คุณสังเกตเห็นคำตอบนี้หรือไม่? จะได้รับ 5 ปีต่อมาจากการยอมรับ แต่มี "รวมแบตเตอรี่"
raratiru

คำตอบ:


73

สิ่งนี้ดูเหมือนจะพกพาระหว่าง Bash และ Korn:

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

บรรทัดที่คล้ายกับสิ่งนี้หรือการมอบหมายเช่นpathname="$_"(ด้วยการทดสอบและการกระทำในภายหลัง) จะต้องอยู่ในบรรทัดแรกของสคริปต์หรือบนบรรทัดหลัง shebang (ซึ่งหากใช้ควรเป็น ksh เพื่อให้ทำงานได้ สถานการณ์ส่วนใหญ่)


10
น่าเสียดายที่มันไม่รับประกันว่าจะทำงาน หากผู้ใช้มีการตั้งค่าBASH_ENV, ที่ด้านบนของสคริปต์ที่จะวิ่งจากคำสั่งสุดท้าย$_ BASH_ENV
มิเคล

30
สิ่งนี้จะไม่ทำงานหากคุณใช้ bash เพื่อรันสคริปต์เช่น $ bash script.sh ดังนั้น $ _ จะเป็น / bin / bash แทน. /script.sh ซึ่งเป็นกรณีที่คุณคาดหวังเมื่อสคริปต์ถูกเรียกใช้ ด้วยวิธีนี้: $ ./script.sh ไม่ว่าในกรณีใดการตรวจสอบด้วย$_จะเป็นปัญหา
Wirawan Purwanto

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

8
น่าเสียดายที่มันผิด! ดูคำตอบของฉัน
F. Hauri

8
เพื่อสรุป: ในขณะที่วิธีการนี้มักจะทำงานได้ก็คือไม่แข็งแกร่ง ; มันล้มเหลวใน 2 สถานการณ์ต่อไปนี้: (a) bash script(การเรียกใช้ผ่านเชลล์ที่ปฏิบัติการได้ซึ่งโซลูชันนี้บิดเบือนแหล่งข้อมูล ) และ (b) (มีโอกาสน้อยกว่า) echo bash; . script(หาก$_เกิดขึ้นเพื่อให้ตรงกับเชลล์ที่จัดหาสคริปต์โซลูชันนี้จะsubshell ) เพียงเปลือกเฉพาะตัวแปรพิเศษ (เช่น$BASH_SOURCE) อนุญาตให้โซลูชั่นที่มีประสิทธิภาพ (มันตามที่ไม่มีการแก้ปัญหาที่สอดคล้องกับ POSIX แข็งแกร่ง) มีความเป็นไปได้ที่จะสร้างการทดสอบข้ามเชลล์ที่มีประสิทธิภาพ
mklement0

170

หากเวอร์ชัน Bash ของคุณรู้เกี่ยวกับตัวแปรอาร์เรย์ BASH_SOURCE ให้ลองดังนี้:

# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1

[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."

11
นั่นอาจเป็นวิธีที่สะอาดที่สุดเนื่องจาก $ BASH_SOURCE นั้นมีจุดประสงค์เพื่อจุดประสงค์นั้นอย่างแท้จริง
con-f-use

4
โปรดทราบว่านี่จะไม่ทำงานภายใต้ ksh ซึ่งเป็นเงื่อนไขที่ OP ระบุไว้
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

2
มีเหตุผลที่จะใช้${BASH_SOURCE[0]}แทนที่จะเป็นเพียงแค่$BASH_SOURCE? และ${0}vs $0?
hraban

4
BASH_SOURCEเป็นตัวแปรอาเรย์ (ดูคู่มือ ) ที่เก็บร่องรอยสแต็กของแหล่งที่มาซึ่ง${BASH_SOURCE[0]}เป็นที่ล่าสุด เครื่องมือจัดฟันจะใช้ที่นี่เพื่อบอกทุบตีสิ่งที่เป็นส่วนหนึ่งของชื่อตัวแปร พวกเขาไม่จำเป็นสำหรับ$0ในกรณีนี้ แต่พวกเขาไม่ได้เจ็บอย่างใดอย่างหนึ่ง ;)
Konrad

4
@ Konrad และถ้าคุณขยาย $arrayคุณจะได้รับ${array[0]}ตามค่าเริ่มต้น ดังนั้นอีกครั้งมีเหตุผล [... ]?
Charles Duffy

133

การแก้ปัญหาที่มีประสิทธิภาพสำหรับbash, ksh,zshรวมทั้งข้ามเปลือกหนึ่งบวกกับวิธีการแก้ปัญหาที่สอดคล้องกับ POSIX ที่แข็งแกร่งพอสมควร :

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

  • การใช้ POSIX ให้บริการเท่านั้น (เช่นในdashซึ่งทำหน้าที่เป็น /bin/shบน Ubuntu) มีไม่มีประสิทธิภาพวิธีการตรวจสอบว่าสคริปต์จะถูกมา - ดูด้านล่างที่ดีที่สุดสำหรับการประมาณ

One-liners follow - คำอธิบายด้านล่าง; รุ่น cross-shell นั้นซับซ้อน แต่ควรทำงานได้อย่างสมบูรณ์:

  • bash (ตรวจสอบแล้วใน 3.57 และ 4.4.19)

    (return 0 2>/dev/null) && sourced=1 || sourced=0
  • ksh (ยืนยันบน 93u +)

    [[ $(cd "$(dirname -- "$0")" && 
       printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
         sourced=1 || sourced=0
  • zsh (ตรวจสอบแล้วเมื่อ 5.0.5) - โปรดเรียกใช้ฟังก์ชั่นนี้นอกฟังก์ชั่น

    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
  • cross-shell (bash, ksh, zsh)

    ([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] || 
     [[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
        printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] || 
     [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
  • สอดคล้องกับ POSIX ; ไม่ใช่สายการบินเดียว (ไปป์ไลน์เดียว) ด้วยเหตุผลทางเทคนิคและไม่เต็มที่ที่แข็งแกร่ง (ดูด้านล่าง):

    sourced=0
    if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
      case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
    elif [ -n "$KSH_VERSION" ]; then
      [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
    elif [ -n "$BASH_VERSION" ]; then
      (return 0 2>/dev/null) && sourced=1 
    else # All other shells: examine $0 for known shell binary filenames
      # Detects `sh` and `dash`; add additional shell filenames as needed.
      case ${0##*/} in sh|dash) sourced=1;; esac
    fi

คำอธิบาย:


ทุบตี

(return 0 2>/dev/null) && sourced=1 || sourced=0

หมายเหตุ: เทคนิคนี้ดัดแปลงมาจากคำตอบของ user5754163เนื่องจากมีความแข็งแกร่งกว่าโซลูชันเดิม[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0[1]

  • ทุบตีช่วยให้ returnงบเฉพาะจากฟังก์ชั่นและในขอบเขตระดับบนสุดของสคริปต์เท่านั้นถ้าสคริปต์มา

    • หากreturnใช้ในขอบเขตระดับบนสุดของสคริปต์ที่ไม่ได้มาข้อความแสดงข้อผิดพลาดจะถูกปล่อยออกมาและรหัสออกถูกตั้งค่าเป็น1สคริปต์ข้อผิดพลาดถูกปล่อยออกมาและรหัสทางออกที่ถูกกำหนดเป็น
  • (return 0 2>/dev/null)ดำเนินการreturnในsubshellและระงับข้อผิดพลาด; หลังจากนั้นรหัสทางออกบ่งชี้ว่าสคริปต์นั้นมีแหล่งที่มา ( 0) หรือไม่ ( 1) ซึ่งใช้กับ&&||ดำเนินการและเพื่อตั้งค่าsourcedตัวแปรให้สอดคล้องกัน

    • จำเป็นต้องใช้ subshell เนื่องจากการดำเนินการ returnในขอบเขตระดับบนสุดของสคริปต์ที่มาจะออกจากสคริปต์
    • เคล็ดลับของหมวกจะ@Haozhunที่ทำคำสั่งที่แข็งแกร่งมากขึ้นอย่างชัดเจนโดยใช้0เป็นreturnตัวถูกดำเนินการ; เขาบันทึก: ต่อการช่วยเหลือของทุบตีreturn [N]: "ถ้าไม่มีการละเว้น N สถานะการส่งคืนคือคำสั่งสุดท้าย" เป็นผลให้รุ่นก่อนหน้า [ซึ่งใช้เพียงreturnโดยไม่มีตัวถูกดำเนินการ] สร้างผลลัพธ์ที่ไม่ถูกต้องหากคำสั่งสุดท้ายบนเชลล์ของผู้ใช้มีค่าส่งคืนที่ไม่เป็นศูนย์

ksh

[[ \
   $(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
   "${.sh.file}" \
]] && 
sourced=1 || sourced=0

ตัวแปรพิเศษ${.sh.file}ค่อนข้างคล้ายคลึงกับ$BASH_SOURCE; โปรดทราบว่า${.sh.file}ทำให้เกิดข้อผิดพลาดทางไวยากรณ์ใน bash, zsh และ dash ดังนั้นโปรดดำเนินการตามเงื่อนไขในสคริปต์หลายเชลล์

แตกต่างจาก bash $0และ${.sh.file}ไม่รับประกันว่าจะเหมือนกันในกรณีที่ไม่ได้มาอย่างแน่นอนเนื่องจาก$0อาจเป็นเส้นทางที่สัมพันธ์กันในขณะที่${.sh.file}จะเต็มเสมอเส้นทางดังนั้น$0ต้องแก้ไขเป็นเส้นทางแบบเต็มก่อนที่จะเปรียบเทียบ


zsh

[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0

$ZSH_EVAL_CONTEXTมีข้อมูลเกี่ยวกับบริบทการประเมิน - เรียกสิ่งนี้นอกฟังก์ชั่น ภายในสคริปต์ที่มา [ 's ขอบเขตระดับบนสุด] $ZSH_EVAL_CONTEXT ปลาย:fileด้วย

ข้อแม้: ภายในแทนคำสั่งผนวก zsh :cmdsubstดังนั้นการทดสอบ$ZSH_EVAL_CONTEXTสำหรับการ :file:cmdsubst$มี


การใช้คุณสมบัติ POSIX เท่านั้น

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

ส่วน "วิธีการจัดการคำร้องที่มา" ในคำตอบของฉันนี้กล่าวถึงกรณีขอบที่ไม่สามารถจัดการกับคุณสมบัติ POSIX ในรายละเอียดเท่านั้น

สิ่งนี้ขึ้นอยู่กับพฤติกรรมมาตรฐาน$0ซึ่งzshไม่สามารถทำได้จัดแสดง

ดังนั้นวิธีที่ปลอดภัยที่สุดคือการรวมวิธีการเฉพาะเชลล์ที่แข็งแกร่งไว้ด้านบนเข้ากับโซลูชันทางเลือกสำหรับเชลล์ที่เหลือทั้งหมด

เคล็ดลับของหมวกกับStéphane Desneuxและคำตอบของเขาสำหรับแรงบันดาลใจ (เปลี่ยนคำแถลงข้ามเปลือกของฉันให้เป็นคำสั่งที่shเข้ากันได้ifและเพิ่มตัวจัดการสำหรับเชลล์อื่น ๆ )

sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
  (return 0 2>/dev/null) && sourced=1 
else # All other shells: examine $0 for known shell binary filenames
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|dash) sourced=1;; esac
fi

[1] user1902689ค้นพบว่า[[ $0 != "$BASH_SOURCE" ]]ให้ผลบวกที่ผิดพลาดเมื่อคุณเรียกใช้งานสคริปต์ที่อยู่ใน$PATHโดยส่งชื่อไฟล์ไปยังbashไบนารีเท่านั้น เช่นbash my-scriptเพราะ$0เป็นแล้วก็my-scriptในขณะที่$BASH_SOURCEเป็นเส้นทางแบบเต็ม ในขณะที่คุณตามปกติจะไม่ใช้เทคนิคนี้ในการเรียกใช้สคริปต์ใน$PATH- คุณต้องการเพียงแค่เรียกพวกเขาโดยตรง ( my-script) - มันเป็นประโยชน์เมื่อรวมกับ-xสำหรับการแก้จุดบกพร่อง


1
ความรุ่งโรจน์สำหรับคำตอบที่ครอบคลุมเช่นนี้
DrUseful

75

หลังจากอ่านคำตอบ @ DennisWilliamson มีปัญหาบางอย่างดูด้านล่าง:

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

ง่าย ทาง

[ "$0" = "$BASH_SOURCE" ]

มาลองกันดูนะคะเพราะการทุบนั้นทำได้ ;-):

source <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

bash <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)

ฉันใช้sourceแทน.เพื่อความสะดวกในการอ่าน (ตามที่.เป็นนามแฝงsource):

. <(echo $'#!/bin/bash
           [ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
           echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)

หมายเหตุว่าจำนวนกระบวนการไม่เปลี่ยนแปลงในขณะที่การเข้าพักขั้นตอนที่มา :

echo $$
29301

ทำไมไม่ใช้$_ == $0การเปรียบเทียบ

เพื่อความมั่นใจในหลาย ๆ กรณีฉันเริ่มเขียนสคริปต์จริง :

#!/bin/bash

# As $_ could be used only once, uncomment one of two following lines

#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell

[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"

คัดลอกไฟล์นี้ไปยังไฟล์ชื่อtestscript:

cat >testscript   
chmod +x testscript

ตอนนี้เราสามารถทดสอบ:

./testscript 
proc: 25758[ppid:24890] is own (DW purpose: subshell)

ไม่เป็นไร.

. ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

source ./testscript 
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)

ไม่เป็นไร.

แต่สำหรับการทดสอบสคริปต์ก่อนที่จะเพิ่มการ-xตั้งค่าสถานะ:

bash ./testscript 
proc: 25776[ppid:24890] is own (DW purpose: sourced)

หรือใช้ตัวแปรที่กำหนดไว้ล่วงหน้า:

env PATH=/tmp/bintemp:$PATH ./testscript 
proc: 25948[ppid:24890] is own (DW purpose: sourced)

env SOMETHING=PREDEFINED ./testscript 
proc: 25972[ppid:24890] is own (DW purpose: sourced)

สิ่งนี้จะไม่ทำงานอีกต่อไป

การย้ายความคิดเห็นจากบรรทัดที่ 5 ถึงอันดับที่ 6 จะให้คำตอบที่อ่านง่ายขึ้น:

./testscript 
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own

. testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

source testscript 
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced

bash testscript 
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own

env FILE=/dev/null ./testscript 
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own

ยาก: ตอนนี้ ...

อย่างที่ฉันไม่ได้ใช้ มากหลังจากอ่านบนหน้า man แล้วก็ลองของฉัน:

#!/bin/ksh

set >/tmp/ksh-$$.log

คัดลอกสิ่งนี้ในtestfile.ksh:

cat >testfile.ksh
chmod +x testfile.ksh

กว่าใช้มันสองครั้ง:

./testfile.ksh
. ./testfile.ksh

ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user   2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user   2140 avr 11 13:48 /tmp/ksh-9781.log

echo $$
9725

และดู:

diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
>   lineno=0
> SHLVL=3

diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
<   level=1
<   lineno=1
< SHLVL=2

มีตัวแปรบางอย่างที่สืบทอดในการทำงานที่มาแต่ไม่มีอะไรเกี่ยวข้อง ...

คุณสามารถตรวจสอบได้ว่า$SECONDSอยู่ใกล้กับ0.000แต่มั่นใจได้ว่ากรณีที่มาด้วยตนเองเท่านั้น...

คุณสามารถลองตรวจสอบสิ่งที่ผู้ปกครองคือ:

วางสิ่งนี้ลงในtestfile.ksh:

ps $PPID

กว่า:

./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32320 pts/4    Ss     0:00 -ksh

. ./testfile.ksh
  PID TTY      STAT   TIME COMMAND
32319 ?        S      0:00 sshd: user@pts/4

หรือps ho cmd $PPIDแต่ใช้งานได้เฉพาะกับหนึ่งระดับของการสมัคร ...

ขออภัยฉันไม่สามารถหาวิธีที่เชื่อถือได้ในการทำเช่นนั้นภายใต้ .


[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ]สำหรับสคริปต์ที่อ่านผ่านทางไพพ์ ( cat script | bash)
hakre

2
โปรดทราบว่า.ไม่ใช่นามแฝงสำหรับsourceแต่จริงๆแล้วเป็นวิธีอื่น source somescript.shเป็น Bash-ism และไม่พกพาได้. somescript.shเป็น POSIX และ IIRC แบบพกพา
dragon788

32

BASH_SOURCE[]คำตอบ (ทุบตี-3.0 และต่อมา) ดูเหมือนง่ายแม้ว่าBASH_SOURCE[]จะไม่ได้รับการบันทึกไว้ในการทำงานนอกร่างกายทำงาน (มันกำลังเกิดขึ้นในการทำงานในความขัดแย้งกับหน้าคน)

วิธีที่แข็งแกร่งที่สุดตามที่ 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 issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(ใน bash-4.2 และใหม่กว่าคุณสามารถใช้แบบฟอร์มที่ง่ายกว่า${FUNCNAME[-1]}แทนรายการสุดท้ายในอาร์เรย์ได้รับการปรับปรุงและทำให้ง่ายขึ้นด้วยความเห็นของ Dennis Williamson ด้านล่าง)

อย่างไรก็ตามปัญหาของคุณตามที่ระบุไว้คือ " ฉันมีสคริปต์ที่ฉันไม่ต้องการให้เรียกว่า" ออก "หากมีที่มา " bashสำนวนทั่วไปสำหรับสถานการณ์นี้คือ:

return 2>/dev/null || exit

หากสคริปต์นั้นมีที่มาจากนั้นก็returnจะยุติสคริปต์ที่มาและกลับไปที่ผู้โทร

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

น่าเศร้าที่นี่ใช้งานไม่ได้ksh(อย่างน้อยไม่ได้อยู่ในรุ่น AT&T ที่ฉันมีที่นี่) มันจะถือว่าreturnเทียบเท่ากับexitถ้าถูกเรียกออกไปข้างนอกฟังก์ชั่นหรือสคริปต์จุดที่มา

อัปเดต : สิ่งที่คุณสามารถทำได้ในรุ่นร่วมสมัยkshคือการตรวจสอบตัวแปรพิเศษ.sh.levelที่ตั้งค่าเป็นความลึกการเรียกใช้ฟังก์ชัน สำหรับสคริปต์ที่เรียกใช้สิ่งนี้จะถูกยกเลิกการตั้งค่าในตอนแรกสำหรับสคริปต์แบบ dot-sourced สคริปต์จะถูกตั้งค่าเป็น 1

function issourced {
    [[ ${.sh.level} -eq 2 ]]
}

issourced && echo this script is sourced

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

(คุณอาจสนใจโค้ดนี้บน github ซึ่งใช้kshฟังก์ชันวินัยและกลอุบายดักจับ debug เพื่อเลียนแบบ bash FUNCNAMEarray)

คำตอบที่ยอมรับได้ที่นี่: http://mywiki.wooledge.org/BashFAQ/109ยังมี$-ตัวบ่งชี้อื่น (แม้ว่าจะไม่สมบูรณ์) ของสถานะเชลล์


หมายเหตุ:

  • มันเป็นไปได้ที่จะสร้างฟังก์ชั่นทุบตีชื่อ "main" และ "source" ( แทนที่ builtin ), ชื่อเหล่านี้อาจปรากฏในFUNCNAME[]แต่ตราบใดที่รายการสุดท้ายเท่านั้นในอาร์เรย์นั้นถูกทดสอบไม่มีความคลุมเครือ
  • pdkshฉันไม่ได้มีคำตอบที่ดีสำหรับ สิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถหาใช้ได้กับpdkshที่ซึ่งการจัดหาสคริปต์แต่ละครั้งจะเปิดตัวอธิบายไฟล์ใหม่ (เริ่มต้นด้วย 10 สำหรับสคริปต์ต้นฉบับ) เกือบจะแน่นอนไม่ใช่สิ่งที่คุณต้องการที่จะพึ่งพา ...

วิธีการเกี่ยวกับ${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}ที่จะได้รับที่ผ่านมา (ล่าง) รายการในกอง? จากนั้นการทดสอบกับ "main" (negate สำหรับ OP) นั้นน่าเชื่อถือที่สุดสำหรับฉัน
เอเดรียนGünter

ถ้าฉันมีPROMPT_COMMANDชุดแสดงให้เห็นว่าขึ้นเป็นดัชนีสุดท้ายของอาร์เรย์ถ้าผมทำงานFUNCNAME source sourcetest.shInverting การตรวจสอบ (มองหาmainเป็นดัชนีที่ผ่านมา) is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }ดูเหมือนว่าแข็งแกร่งมากขึ้น:
dimo414

1
สถานะ man-page ที่FUNCNAMEมีเฉพาะในฟังก์ชั่น ตามที่การทดสอบของฉันกับdeclare -p FUNCNAME, bashทำงานแตกต่างกัน V4.3 ให้เกิดข้อผิดพลาดนอกฟังก์ชั่นในขณะ v4.4 declare -a FUNCNAMEให้ ทั้งสอง (!) กลับมาmainหา${FUNCNAME[0]}ในสคริปต์หลัก (ถ้าถูกเรียกใช้) ในขณะที่$FUNCNAMEไม่ได้ให้อะไรเลย และ: มีสคริปต์จำนวนมากออกมาที่นั่น "ab" โดยใช้$BASH_SOURCEฟังก์ชั่นภายนอกฉันสงสัยว่ามันจะเปลี่ยนหรือ
Tino

24

หมายเหตุจากบรรณาธิการ: คำตอบของคำตอบนี้ทำงานได้อย่างสมบูรณ์ แต่เป็นbash- เพียงอย่างเดียว
(return 2>/dev/null)มันสามารถที่จะคล่องตัว

TL; DR

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

วางสิ่งนี้ลงในไฟล์แล้วเรียกมันว่า test.sh:

#!/usr/bin/env sh

# Try to execute a `return` statement,
# but do it in a sub-shell and catch the results.
# If this script isn't sourced, that will raise an error.
$(return >/dev/null 2>&1)

# What exit code did that give?
if [ "$?" -eq "0" ]
then
    echo "This script is sourced."
else
    echo "This script is not sourced."
fi

ดำเนินการโดยตรง

shell-prompt> sh test.sh
output: This script is not sourced.

แหล่งที่มา:

shell-prompt> source test.sh
output: This script is sourced.

สำหรับฉันมันใช้งานได้ใน zsh และ bash

คำอธิบาย

returnคำสั่งจะยกข้อผิดพลาดถ้าคุณพยายามที่จะดำเนินการได้ที่ด้านนอกของฟังก์ชั่นหรือถ้าสคริปต์ไม่ได้มา ลองสิ่งนี้จาก shell prompt:

shell-prompt> return
output: ...can only `return` from a function or sourced script

คุณไม่จำเป็นต้องเห็นข้อความแสดงข้อผิดพลาดดังนั้นคุณสามารถเปลี่ยนเส้นทางไปยัง dev / null:

shell-prompt> return >/dev/null 2>&1

ตอนนี้ตรวจสอบรหัสออก 0 หมายถึงตกลง (ไม่มีข้อผิดพลาดเกิดขึ้น) 1 หมายถึงข้อผิดพลาดเกิดขึ้น:

shell-prompt> echo $?
output: 1

คุณยังต้องการรันreturnคำสั่งภายในเชลล์ย่อย เมื่อreturnคำสั่งรัน . . ดี . . ผลตอบแทน หากคุณเรียกใช้งานในเชลล์ย่อยมันจะส่งคืนจากเชลล์ย่อยนั้นแทนที่จะส่งคืนสคริปต์ของคุณ ในการดำเนินการใน sub-shell, wrap ใน$(...):

shell-prompt> $(return >/dev/null 2>$1)

ตอนนี้คุณสามารถเห็นโค้ดทางออกของ sub-shell ซึ่งควรเป็น 1 เนื่องจากมีข้อผิดพลาดเกิดขึ้นภายใน sub-shell:

shell-prompt> echo $?
output: 1

สิ่งนี้ล้มเหลวสำหรับฉันใน 0.5.8-2.1ubuntu2 $ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced.
Phil Rutschman

3
POSIX ไม่ได้ระบุสิ่งที่returnควรทำที่ระดับบนสุด ( pubs.opengroup.org/onlinepubs/9699919799/utilities/ … ) dashเปลือกถือว่าที่ระดับบนสุดเป็นreturn exitกระสุนอื่น ๆ ที่ชอบbashหรือzshไม่อนุญาตreturnในระดับบนสุดซึ่งเป็นคุณสมบัติที่ใช้เทคนิคเช่นการหาช่องโหว่นี้
user5754163

มันทำงานในดวลจุดโทษถ้าคุณลบออก$ก่อนที่ subshell นั่นคือใช้(return >/dev/null 2>&1)แทน$(return >/dev/null 2>&1)- แต่จากนั้นจะหยุดทำงานในการทุบตี
บาร์

@Eponymous: ตั้งแต่dashที่การแก้ปัญหานี้ไม่ได้ทำงานทำหน้าที่เป็นshบน Ubuntu ตัวอย่างเช่นการแก้ปัญหานี้ไม่ได้โดยทั่วไปshการทำงานร่วมกับ วิธีแก้ปัญหาใช้งานได้สำหรับฉันใน Bash 3.2.57 และ 4.4.5 - มีหรือไม่มี$ก่อน(...)(แม้ว่าจะไม่มีเหตุผลที่ดีสำหรับ$)
mklement0

2
returnไอเอ็นจีจะไม่มีการคืนค่า explcit เมื่อsourceไอเอ็นจีสคริปต์หลังจากคำสั่งที่ไม่ดีออก เสนอให้มีการแก้ไขปรับปรุง
DimG

12

FWIW หลังจากอ่านคำตอบอื่น ๆ ทั้งหมดฉันได้คำตอบสำหรับฉัน:

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

สิ่งนี้ใช้ได้กับสคริปต์ทั้งหมดซึ่งเริ่มต้นด้วย#!/bin/bashแต่อาจมีที่มาจากเชลล์ที่แตกต่างกันเช่นกันเพื่อเรียนรู้ข้อมูล (เช่นการตั้งค่า) ซึ่งเก็บไว้ภายนอกmainฟังก์ชั่น

ตามความคิดเห็นด้านล่างคำตอบที่นี่เห็นได้ชัดว่าไม่ได้ทำงานกับbashตัวแปรทั้งหมด นอกจากนี้ยังไม่ได้สำหรับระบบที่อยู่บนพื้นฐานของ/bin/sh bashIE มันล้มเหลวสำหรับbashv3.x บน MacOS (ปัจจุบันฉันไม่รู้วิธีแก้ปัญหานี้)

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)

# this main() function is only meant to be meaningful for bash
main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

แทนที่จะเป็น 2 บรรทัดสุดท้ายคุณสามารถใช้โค้ดต่อไปนี้ (ในความเห็นของฉันอ่านได้น้อยกว่า) เพื่อไม่ตั้งBASH_SOURCEในเชลล์อื่นและอนุญาตให้set -eทำงานในmain:

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

สคริปต์สูตรนี้มีคุณสมบัติดังต่อไปนี้:

  • หากดำเนินการโดยbashวิธีปกติmainจะเรียกว่า โปรดทราบว่านี่ไม่รวมการโทรเช่นbash -x script(ที่scriptไม่มีเส้นทาง) ดูด้านล่าง

  • หาก sourced โดยbash, mainเรียกว่าเพียงถ้าสคริปต์เรียกเกิดขึ้นจะมีชื่อเดียวกัน (ตัวอย่างเช่นถ้ามันมาเองหรือผ่านbash -c 'someotherscript "$@"' main-script args..ที่main-scriptจะต้องเป็นสิ่งที่testเห็นว่าเป็น$BASH_SOURCE)

  • ถ้ามา / ดำเนินการ / อ่าน / evaled โดยเปลือกอื่น ๆ กว่าbash, mainไม่ได้เรียกว่า ( BASH_SOURCEอยู่เสมอที่แตกต่างกันไป$0)

  • mainจะไม่ถูกเรียกถ้าbashอ่านสคริปต์จาก stdin ยกเว้นว่าคุณตั้ง$0เป็นสตริงว่างเช่น:( exec -a '' /bin/bash ) <script

  • หากประเมินโดยbashwith eval ( eval "`cat script`" เครื่องหมายคำพูดทั้งหมดมีความสำคัญ! ) จากภายในสคริปต์อื่นการโทรmainนี้ หากevalรันจาก commandline โดยตรงสิ่งนี้จะคล้ายกับกรณีก่อนหน้าซึ่งสคริปต์ถูกอ่านจาก stdin ( BASH_SOURCEว่างเปล่า$0โดยปกติแล้ว/bin/bashหากไม่ได้บังคับให้ทำสิ่งที่แตกต่างอย่างสิ้นเชิง)

  • ถ้าmainไม่ถูกเรียกมันจะส่งคืนtrue( $?=0)

  • สิ่งนี้ไม่ได้ขึ้นอยู่กับพฤติกรรมที่ไม่คาดคิด (ก่อนหน้านี้ฉันเขียนเอกสารที่ไม่มีเอกสาร แต่ฉันไม่พบเอกสารใด ๆ ที่คุณไม่สามารถunsetเปลี่ยนแปลงBASH_SOURCEได้)

    • BASH_SOURCEเป็นทุบตีลิขสิทธิ์อาร์เรย์ แต่การอนุญาตให้BASH_SOURCE=".$0"เปลี่ยนแปลงมันจะเปิดกระป๋องหนอนที่อันตรายมากดังนั้นความคาดหวังของฉันคือว่าสิ่งนี้จะต้องไม่มีผลกระทบ (ยกเว้นบางทีอาจมีคำเตือนที่น่าเกลียดปรากฏขึ้นในบางรุ่นในอนาคตbash)
    • ไม่มีเอกสารที่ใช้BASH_SOURCEงานได้นอกฟังก์ชั่น อย่างไรก็ตามสิ่งที่ตรงกันข้าม (ที่ใช้งานได้ในฟังก์ชั่นเท่านั้น) นั้นไม่มีการจัดทำเป็นเอกสาร การสังเกตคือมันใช้งานได้ (ทดสอบด้วยbashv4.3 และ v4.4 โชคไม่ดีที่ฉันไม่มีbashv3.x อีกต่อไป) และสคริปต์จำนวนมากเกินไปที่จะแตกถ้า$BASH_SOURCEหยุดทำงานตามที่สังเกตไว้ ดังนั้นความคาดหวังของฉันคือBASH_SOURCEยังคงเป็นเช่นเดียวกับรุ่นในอนาคตของbashเกินไป
    • ในทางตรงกันข้าม (หาดี BTW!) พิจารณา( return 0 )ซึ่งจะให้0ถ้ามีที่มาและ1ถ้าไม่ได้มา นี่เป็นสิ่งที่คาดไม่ถึงไม่เพียง แต่สำหรับฉันและ (ตามที่มีการอ่าน) POSIX บอกว่าreturnจาก subshell เป็นพฤติกรรมที่ไม่ได้กำหนด (และที่returnนี่ชัดเจนจาก subshell) บางทีในที่สุดฟีเจอร์นี้ก็มีการใช้อย่างแพร่หลายมากจนไม่สามารถเปลี่ยนแปลงได้อีกต่อไป แต่ AFAICS มีโอกาสสูงกว่ามากที่bashเวอร์ชันในอนาคตบางรุ่นจะเปลี่ยนพฤติกรรมการส่งคืนโดยบังเอิญ
  • แต่น่าเสียดายที่ไม่ได้ทำงานbash -x script 1 2 3 main(เปรียบเทียบscript 1 2 3ที่scriptไม่มีเส้นทาง) การใช้ต่อไปนี้สามารถใช้เป็นวิธีแก้ปัญหา:

    • bash -x "`which script`" 1 2 3
    • bash -xc '. script' "`which script`" 1 2 3
    • ที่bash script 1 2 3ไม่ได้ทำงานmainถือได้ว่าเป็นคุณสมบัติ
  • โปรดทราบว่าการ( exec -a none script )โทรmain( bashไม่ส่งผ่าน$0ไปยังสคริปต์สำหรับสิ่งนี้คุณต้องใช้-cตามที่แสดงในจุดสุดท้าย)

ดังนั้นยกเว้นบางกรณีที่มุมบางอย่างmainจะถูกเรียกเท่านั้นเมื่อสคริปต์ถูกดำเนินการตามปกติ โดยปกตินี่คือสิ่งที่คุณต้องการโดยเฉพาะอย่างยิ่งเพราะมันไม่มีโค้ดที่เข้าใจยาก

โปรดทราบว่ามันคล้ายกับรหัส Python:

if __name__ == '__main__': main()

ซึ่งยังป้องกันการโทรmainยกเว้นในบางกรณีคุณสามารถนำเข้า / โหลดสคริปต์และบังคับใช้__name__='__main__'

ทำไมฉันถึงคิดว่านี่เป็นวิธีทั่วไปที่ดีในการแก้ปัญหา

หากคุณมีบางสิ่งบางอย่างซึ่งสามารถหาได้จากหลาย ๆ เชลล์มันต้องเข้ากันได้ อย่างไรก็ตาม (อ่านคำตอบอื่น ๆ ) เนื่องจากไม่มีวิธีพกพา (ใช้งานง่าย) ในการตรวจจับsourceไอเอ็นจีคุณควรเปลี่ยนกฎคุณควรเปลี่ยนกฎ

โดยการบังคับให้สคริปต์ต้องถูกดำเนินการโดย /bin/bashคุณทำสิ่งนี้อย่างแน่นอน

วิธีนี้จะแก้ปัญหาทุกกรณี แต่การทำตามในกรณีที่สคริปต์ไม่สามารถเรียกใช้โดยตรง:

  • /bin/bash ไม่ได้ติดตั้งหรือไม่ทำงาน (i. E. ในสภาพแวดล้อมการบูต)
  • ถ้าคุณไปป์มันเหมือนเปลือกหอย curl https://example.com/script | $SHELL
  • (หมายเหตุ: นี่เป็นความจริงหากว่าคุณbashเพิ่งจะพอสูตรสูตรนี้จะรายงานว่าล้มเหลวสำหรับตัวแปรบางตัวดังนั้นโปรดตรวจสอบให้แน่ใจว่ามันใช้งานได้กับกรณีของคุณ)

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

  • $SHELL -c '. script && main'
  • { curl https://example.com/script && echo && echo main; } | $SHELL
  • $SHELL -c 'eval "`curl https://example.com/script`" && main'
  • echo 'eval "`curl https://example.com/script`" && main' | $SHELL

หมายเหตุ

  • คำตอบนี้คงเป็นไปไม่ได้หากปราศจากความช่วยเหลือจากคำตอบอื่น ๆ ! แม้แต่คนผิด - ซึ่งทำให้ฉันโพสต์สิ่งนี้ในตอนแรก

  • อัปเดต: แก้ไขเนื่องจากการค้นพบใหม่ที่พบในhttps://stackoverflow.com/a/28776166/490291


ผ่านการทดสอบสำหรับ ksh และ bash-4.3 ดี มันช่างน่าเสียดายที่คำตอบของคุณจะมีชีวิตที่ยากลำบากเพราะคำตอบอื่น ๆ นั้นมีมานานหลายปีแล้วในการรวบรวมคะแนนเสียง
hagello

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

@Tino: สำหรับ "อาจมีแหล่งที่มาของเชลล์ที่แตกต่างกันเช่นกัน": สำหรับ macOS ซึ่ง/bin/shมีประสิทธิภาพbashในโหมด POSIX การกำหนดให้BASH_SOURCE แบ่งสคริปต์ของคุณ ในเปลือกหอยอื่น ๆ ( dash, ksh, zsh) เรียกสคริปต์ของคุณโดยผ่านมันเป็นอาร์กิวเมนต์ไฟล์โดยตรงกับปฏิบัติการเปลือกทำงานผิดปกติ (เช่นzsh <your-script>จะทำให้สคริปต์ของคุณเข้าใจผิดคิดว่ามันเป็นที่มา ) (คุณแล้วพูดถึงว่าท่อทำงานผิดปกติรหัสในเปลือกหอยทั้งหมด.)
mklement0

@Tino: นอกเหนือจาก: ในขณะที่. <your-script>(การจัดหา) ทำงานจากเชลล์เหมือน POSIX ทั้งหมดในหลักการมันจะเหมาะสมถ้าสคริปต์ถูกเขียนอย่างชัดเจนเพื่อใช้คุณสมบัติ POSIX เท่านั้นเพื่อป้องกันไม่ให้คุณสมบัติเฉพาะเปลือกหนึ่งจากการดำเนินการทำลาย ในเปลือกหอยอื่น ๆ การใช้Bash Shebang Line (มากกว่า#!/bin/sh) จึงทำให้เกิดความสับสน - อย่างน้อยก็ไม่มีความเห็นที่ชัดเจน ในทางกลับกันถ้าสคริปต์ของคุณถูกเรียกใช้จาก Bash เท่านั้น (แม้ว่าจะไม่ได้พิจารณาคุณสมบัติที่ไม่สามารถพกพาได้) ก็ควรที่จะปฏิเสธการดำเนินการในเชลล์ที่ไม่ใช่ Bash
mklement0

1
@ mklement0 ขอบคุณอีกครั้งเพิ่มหมายเหตุว่ามีปัญหา สำหรับผู้อ่านอื่น ๆ :เมื่อมากับ bash v3.x มันไม่ควรดำเนินการmainแต่มันทำในกรณีนี้! และเมื่อมีที่มา/bin/shซึ่งก็bash --posixเหมือนกันเกิดขึ้นในกรณีนี้และนั่นก็เป็นความผิดที่ธรรมดาเช่นกัน
Tino

6

ใช้งานได้ในภายหลังในสคริปต์และไม่ได้ขึ้นอยู่กับตัวแปร _:

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

หรือ

[ $(basename $0) = $Prog ] && exit

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

5

ฉันจะให้คำตอบเฉพาะ BASH กรณ์เปลือกขออภัย สมมติว่าชื่อสคริปต์ของคุณคือinclude2.sh; แล้วทำให้ฟังก์ชั่นภายในเรียกว่าinclude2.sh am_I_sourcedนี่คือเวอร์ชั่นตัวอย่างของฉันinclude2.sh:

am_I_sourced()
{
  if [ "${FUNCNAME[1]}" = source ]; then
    if [ "$1" = -v ]; then
      echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
    fi
    return 0
  else
    if [ "$1" = -v ]; then
      echo "I am not being sourced, my script/shell name was $0"
    fi
    return 1
  fi
}

if am_I_sourced -v; then
  echo "Do something with sourced script"
else
  echo "Do something with executed script"
fi

ตอนนี้พยายามที่จะรันมันในหลาย ๆ ทาง:

~/toys/bash $ chmod a+x include2.sh

~/toys/bash $ ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ bash ./include2.sh 
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script

~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script

ดังนั้นจึงใช้งานได้โดยไม่มีข้อยกเว้นและไม่ได้ใช้$_สิ่งที่เปราะ เคล็ดลับนี้ใช้เครื่องมืออำนวยความสะดวกในการวิปัสสนาของ BASH นั่นคือตัวแปรในตัวFUNCNAMEและBASH_SOURCE; ดูเอกสารประกอบของพวกเขาในหน้าทุบตีด้วยตนเอง

เพียงสองข้อแม้:

1) การเรียกที่จะam_I_called ต้องเกิดขึ้นในสคริปต์ที่มา แต่ไม่ได้อยู่ในฟังก์ชั่นใด ๆ เพื่อมิให้${FUNCNAME[1]}ส่งกลับอย่างอื่น ใช่ ... คุณสามารถตรวจสอบได้${FUNCNAME[2]}- แต่คุณแค่ทำให้ชีวิตของคุณยากขึ้น

2) ฟังก์ชั่นam_I_called จะต้องอยู่ในสคริปต์ที่มาถ้าคุณต้องการที่จะหาสิ่งที่ชื่อของไฟล์ที่จะถูกรวม


1
การชี้แจง: คุณลักษณะนี้ต้องใช้ BASH เวอร์ชัน 3 ขึ้นไปเพื่อให้ทำงานได้ ใน BASH 2 FUNCNAME เป็นตัวแปรสเกลาร์แทนที่จะเป็นอาร์เรย์ นอกจากนี้ BASH 2 ไม่มีตัวแปรอาร์เรย์ BASH_SOURCE
Wirawan Purwanto

4

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

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

เพราะ[[ไม่ได้รับการยอมรับโดย (ค่อนข้างทวารหนักยึด IMHO) Debian POSIX เข้ากันได้dashเปลือก นอกจากนี้อาจต้องใช้เครื่องหมายคำพูดเพื่อป้องกันชื่อไฟล์ที่มีช่องว่างอีกครั้งในเชลล์ดังกล่าว


2

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

ตัวอย่างเช่นหากผู้ใช้ตั้งไว้BASH_ENVที่ด้านบนของสคริปต์จะ$_มีชื่อของคำสั่งสุดท้ายที่ดำเนินการในBASH_ENVสคริปต์

วิธีที่ดีที่สุดที่ฉันพบคือใช้$0แบบนี้:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

น่าเสียดายที่วิธีนี้ใช้ไม่ได้กับกล่องใน zsh เนื่องจากfunctionargzeroตัวเลือกที่ทำมากกว่าชื่อของมันและเป็นค่าเริ่มต้น

การทำงานรอบนี้ผมใส่ในของฉันunsetopt functionargzero.zshenv


1

ผมทำตามmklement0 การแสดงออกที่มีขนาดกะทัดรัด

มันเรียบร้อย แต่ฉันสังเกตเห็นว่ามันสามารถล้มเหลวในกรณีของ ksh เมื่อเรียกเช่นนี้:

/bin/ksh -c ./myscript.sh

(มันคิดว่ามันมีที่มาและไม่ใช่เพราะมันทำหน้าที่ subshell) แต่นิพจน์จะทำงานเพื่อตรวจจับสิ่งนี้:

/bin/ksh ./myscript.sh

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

ดังนั้นฉันจึงลงท้ายด้วยโค้ดต่อไปนี้ซึ่งใช้งานได้กับ bash, zsh, dash และ ksh

SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then 
    [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
elif [ -n "$KSH_VERSION" ]; then
    [[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1
elif [ -n "$BASH_VERSION" ]; then
    [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
    case $0 in *dash*) SOURCED=1 ;; esac
fi

รู้สึกฟรีเพื่อเพิ่มการสนับสนุนเปลือกแปลกใหม่ :)


ในksh 93+u, ksh ./myscript.shทำงานที่ดีสำหรับฉัน (ด้วยกับคำสั่งของฉัน) - สิ่งที่รุ่นที่คุณใช้?
mklement0

ฉันกลัวมีวิธีที่จะไม่น่าเชื่อถือตรวจสอบว่าสคริปต์จะถูกมาใช้ POSIX-ให้บริการเท่านั้น: ความพยายามของคุณถือว่า Linux ( /proc/$$/cmdline) และมุ่งเน้นไปdashเท่านั้น (ซึ่งยังทำหน้าที่เป็นshบน Ubuntu เป็นต้น) หากคุณยินดีที่จะตั้งสมมติฐานบางอย่างคุณสามารถตรวจสอบ$0แบบทดสอบที่สมเหตุสมผล แต่ไม่สมบูรณ์แบบพกพาได้
mklement0

++ สำหรับวิธีการพื้นฐาน - ฉันใช้เสรีภาพในการปรับตัวเข้ากับสิ่งที่ฉันคิดว่าเป็นการประมาณค่าพกพาที่ดีที่สุดในการสนับสนุนsh/ dashเช่นกันในภาคผนวกของคำตอบของฉัน
mklement0

0

ฉันไม่คิดว่าจะมีวิธีพกพาในการทำเช่นนี้ทั้ง ksh และ bash ในทุบตีคุณสามารถตรวจสอบได้โดยใช้callerผลลัพธ์ แต่ฉันไม่คิดว่ามี ksh เทียบเท่า


$0ทำงานในbash, และksh93 pdkshฉันไม่ต้องksh88ทดสอบ
มิเคล

0

ฉันต้องการสายการบินเดียวที่ทำงานบน [mac, linux] กับ bash.version> = 3 และคำตอบเหล่านี้ไม่ตรงกับใบเรียกเก็บเงิน

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"

1
bashโซลูชั่นที่ทำงานได้ดี (คุณสามารถลดความซับซ้อนของการ$BASH_SOURCE) แต่kshการแก้ปัญหาคือไม่แข็งแกร่ง: ถ้าสคริปต์ของคุณจะถูก sourced โดยสคริปต์อื่นคุณจะได้รับในเชิงบวกเท็จ
mklement0

0

ตรงประเด็น:คุณจะต้องประเมินว่าตัวแปร"$ 0"เท่ากับชื่อของเชลล์หรือไม่


แบบนี้:

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi


ผ่านเปลือก :

$ bash check_source.sh 
First Parameter: check_source.sh

The script WAS NOT sourced.

ผ่านแหล่งข้อมูล :

$ source check_source.sh
First Parameter: bash

The script was sourced.



มันค่อนข้างยากที่จะมีวิธีตรวจจับแบบพกพา 100%ว่าสคริปต์มาจากแหล่งใดหรือไม่

เกี่ยวกับประสบการณ์ของผม(7 ปีกับ Shellscripting) , วิธีที่ปลอดภัยเท่านั้น (ไม่ได้อาศัยอยู่ในสภาพแวดล้อมที่มีตัวแปรPIDsและอื่น ๆ ซึ่งจะไม่ปลอดภัยเนื่องจากความจริงที่ว่ามันเป็นสิ่งที่VARIABLE ), คุณควรจะ:

  • ขยายความเป็นไปได้จากคุณถ้า
  • ใช้สวิตช์ / เคสหากคุณต้องการ

ตัวเลือกทั้งสองไม่สามารถปรับขนาดอัตโนมัติได้ แต่เป็นวิธีที่ปลอดภัยกว่า



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

เมื่อคุณแหล่งสคริปต์ผ่านเซสชัน SSH ค่าที่ส่งคืนโดยตัวแปร"$ 0" (เมื่อใช้แหล่งที่มา ) เป็นทุบตี

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
    echo "The script was sourced."
else
    echo "The script WAS NOT sourced."
fi

หรือ

#!/bin/bash

echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
    echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
    echo "The script was sourced via SSH session."
else
    echo "The script WAS NOT sourced."
fi

2
Downvoted เช่นนี้เป็นสิ่งที่ผิดธรรมดา: ให้/bin/bash -c '. ./check_source.sh' The script WAS NOT sourced.ข้อผิดพลาดเดียวกัน: ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh'->The script WAS NOT sourced.
Tino

2
downvote ของคุณเปลี่ยนสถานการณ์ทั้งหมดและมีส่วนร่วมอย่างมาก Tino ขอบคุณ!
ivanleoncz

0

ฉันลงเอยด้วยการตรวจสอบ [[ $_ == "$(type -p "$0")" ]]

if [[ $_ == "$(type -p "$0")" ]]; then
    echo I am invoked from a sub shell
else
    echo I am invoked from a source command
fi

เมื่อใช้curl ... | bash -s -- ARGSเพื่อเรียกใช้สคริปต์ระยะไกล on-the-fly, $ 0 จะเป็นเพียงbashปกติ/bin/bashเมื่อเรียกใช้ไฟล์สคริปต์จริงดังนั้นฉันใช้type -p "$0"เพื่อแสดงเส้นทางเต็มของทุบตี

ทดสอบ:

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE

wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE

0

นี่เป็นคำตอบที่แยกออกมาจากคำตอบอื่น ๆ ซึ่งเกี่ยวกับการรองรับ cross shell "universal" วิธีนี้คล้ายกับhttps://stackoverflow.com/a/2942183/3220983โดยเฉพาะอย่างยิ่งแม้ว่าจะแตกต่างกันเล็กน้อย จุดอ่อนของสิ่งนี้คือสคริปต์ลูกค้าจะต้องเคารพวิธีใช้ (เช่นโดยการส่งออกตัวแปรก่อน) จุดแข็งคือสิ่งนี้เรียบง่ายและควรทำงานได้ทุกที่ นี่คือแม่แบบสำหรับการตัดและวางของคุณ:

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

หมายเหตุ: ฉันใช้exportเพียงให้แน่ใจว่ากลไกนี้สามารถขยายเข้าไปในกระบวนการย่อย

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