อะไรคือข้อแตกต่างระหว่างการเรียกใช้งานเชลล์สคริปต์โดยใช้“ source file.sh”,“ ./file.sh”,“ sh file.sh”,“ ./file.sh”?


13

ดูรหัส:

#!/bin/bash
read -p "Eneter 1 for UID and 2 for LOGNAME" choice
if [ $choice -eq 1 ]
then
        read -p "Enter UID:  " uid
        logname=`cat /etc/passwd | grep $uid | cut -f1 -d:`
else
        read -p "Enter Logname:  " logname
fi
not=`ps -au$logname | grep -c bash`
echo  "The number of terminals opened by $logname are $not"

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

$ ./file.sh
The number of terminals opened by x are 3

$ bash file.sh
The number of terminals opened by x are 5

$ sh file.sh
The number of terminals opened by x are 3

$ source file.sh
The number of terminals opened by x are 4

$ . ./file.sh
The number of terminals opened by x are 4

หมายเหตุ: ฉันผ่าน 1 และ uid 1000 ไปยังโปรแกรมปฏิบัติการเหล่านี้ทั้งหมด

ตอนนี้คุณช่วยอธิบายความแตกต่างระหว่างสิ่งเหล่านี้ได้ไหม?


ความแตกต่างคือที่เปลือกจะถูกดำเนินการ sh ไม่ใช่คนทุบตี
j0h

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

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

@RamanaReddy ผู้ใช้รายอื่นอาจเรียกใช้สคริปต์หรือเริ่มแท็บใหม่ ใครจะรู้?
muru

คำตอบ:


21

ข้อแตกต่างที่สำคัญเพียงอย่างเดียวคือระหว่างการจัดหาและการเรียกใช้สคริปต์ source foo.shจะเป็นแหล่งที่มาและตัวอย่างอื่น ๆ ทั้งหมดที่คุณแสดงกำลังดำเนินการ รายละเอียดเพิ่มเติม:

  1. ./file.sh

    จะดำเนินการสคริปต์ที่เรียกว่าfile.shที่อยู่ในไดเรกทอรีปัจจุบัน ( ./) โดยปกติเมื่อคุณเรียกใช้commandเชลล์จะค้นหาไดเรกทอรีใน$PATHไฟล์เรียกทำงานที่เรียกว่าของcommandคุณ หากคุณให้เส้นทางแบบเต็มเช่น/usr/bin/commandหรือ./commandดังนั้น$PATHจะถูกละเว้นและไฟล์เฉพาะนั้นจะถูกดำเนินการ

  2. ../file.sh

    โดยทั่วไปจะเหมือนกับ./file.shยกเว้นว่าแทนที่จะค้นหาในไดเรกทอรีปัจจุบันfile.shจะค้นหาในไดเรกทอรีหลัก ( ../)

  3. sh file.sh

    สิ่งนี้เทียบเท่ากับsh ./file.shข้างต้นมันจะเรียกใช้สคริปต์ที่เรียกว่าfile.shในไดเรกทอรีปัจจุบัน ความแตกต่างคือคุณกำลังรันมันอย่างชัดเจนด้วยshเชลล์ ในระบบอูบุนตูที่เป็นและไม่ได้dash bashโดยปกติแล้วสคริปต์จะมีบรรทัด shebangที่ให้โปรแกรมที่ควรรัน การเรียกพวกเขาด้วยการแทนที่ที่แตกต่างนั้น ตัวอย่างเช่น:

    $ cat foo.sh
    #!/bin/bash  
    ## The above is the shebang line, it points to bash
    ps h -p $$ -o args='' | cut -f1 -d' '  ## This will print the name of the shell

    สคริปต์นั้นจะพิมพ์ชื่อของเชลล์ที่ใช้เพื่อรัน มาดูกันว่ามันจะกลับมาเมื่อใดในวิธีที่แตกต่าง:

    $ bash foo.sh
    bash
    $ sh foo.sh 
    sh
    $ zsh foo.sh
    zsh

    ดังนั้นการโทรหาสคริปต์ด้วยshell scriptจะเป็นการแทนที่บรรทัด shebang (ถ้ามี) และเรียกใช้สคริปต์ด้วยเชลล์อะไรก็ตามที่คุณบอก

  4. source file.sh หรือ . file.sh

    สิ่งนี้เรียกว่าน่าแปลกใจพอที่จะจัดหาสคริปต์ คำสำคัญsourceคือนามแฝงของ.คำสั่งshell builtin นี่เป็นวิธีการเรียกใช้งานสคริปต์ภายในเชลล์ปัจจุบัน โดยปกติเมื่อเรียกใช้งานสคริปต์สคริปต์จะรันในเชลล์ของตัวเองซึ่งแตกต่างจากสคริปต์ปัจจุบัน เพื่อแสดง:

    $ cat foo.sh
    #!/bin/bash
    foo="Script"
    echo "Foo (script) is $foo"

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

    $ foo="Parent"
    $ bash foo.sh 
    Foo (script) is Script  ## This is the value from the script's shell
    $ echo "$foo"          
    Parent                  ## The value in the parent shell is unchanged

    อย่างไรก็ตามถ้าฉันแหล่งสคริปต์แทนการดำเนินการมันจะถูกเรียกใช้ในเชลล์เดียวกันดังนั้นค่าของfooในแม่จะมีการเปลี่ยนแปลง:

    $ source ./foo.sh 
    Foo (script) is Script   ## The script's foo
    $ echo "$foo" 
    Script                   ## Because the script was sourced, 
                             ## the value in the parent shell has changed

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


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

#!/bin/bash
logname=terdon
not=`ps -au$logname | grep -c bash`
echo  "The number of shells opened by $logname is $not"

และเรียกใช้ด้วยวิธีการต่าง ๆ โดยเปิดเพียงเทอร์มินัลเดียว:

  1. การเปิดตัวโดยตรง, ./foo.sh.

    $ ./foo.sh
    The number of shells opened by terdon is 1

    ที่นี่คุณใช้สาย shebang ซึ่งหมายความว่าสคริปต์จะถูกเรียกใช้งานโดยตรงจากสิ่งที่ตั้งไว้ที่นั่น psนี้มีผลต่อวิธีการที่สคริปต์ที่ปรากฏอยู่ในการส่งออกของ แทนที่จะแสดงเป็นbash foo.shมันจะปรากฏเฉพาะfoo.shซึ่งหมายความว่าคุณgrepจะพลาด การปกครอง, ทุบตีใช้สคริปต์: มีจริง 3 กรณีทุบตีทำงานเป็นและอีกคนหนึ่งซึ่งทำงานpsคำสั่ง สิ่งสุดท้ายนี้มีความสำคัญการเรียกใช้คำสั่งที่มีการทดแทนคำสั่ง ( `command`หรือ$(command)) ส่งผลให้เกิดสำเนาของเชลล์พาเรนต์ที่จะเปิดตัว อย่างไรก็ตามที่นี่ไม่มีการแสดงสิ่งเหล่านี้เนื่องจากวิธีการpsแสดงผลลัพธ์

  2. เรียกใช้โดยตรงด้วยเชลล์ (bash) ที่ชัดเจน

    $ bash foo.sh 
    The number of shells opened by terdon is 3

    ที่นี่เนื่องจากคุณกำลังใช้งานbash foo.shเอาต์พุตของpsจะแสดงbash foo.shและถูกนับ ดังนั้นที่นี่เรามีการปกครองที่bashใช้สคริปต์และเปลือกโคลน (เรียกใช้ps) แสดงทั้งหมดเพราะตอนนี้จะแสดงแต่ละของพวกเขาเพราะคำสั่งของคุณจะมีคำว่าpsbash

  3. เปิดตัวโดยตรงกับเชลล์ที่แตกต่างกัน ( sh)

    $ sh foo.sh
    The number of shells opened by terdon is 1

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

  4. การจัดหา (ทั้งโดย.หรือsourceในสิ่งเดียวกัน)

    $ . ./foo.sh 
    The number of shells opened by terdon is 2

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


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

pgrep -cu terdon bash

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

#!/usr/bin/env bash
user="terdon"

printf "Open shells:"
pgrep -cu "$user" bash

สิ่งนี้จะส่งคืน 1 เมื่อมีที่มาและ 2 (เนื่องจากมีการเปิดใช้งาน bash ใหม่เพื่อเรียกใช้สคริปต์) สำหรับวิธีการเปิดตัวอื่น ๆ ทั้งหมด มันจะยังคงกลับ 1 เมื่อเปิดตัวด้วยตั้งแต่กระบวนการเด็กไม่ได้shbash


เมื่อคุณพูดว่าการทดแทนคำสั่งเปิดตัวสำเนาของเปลือกหลักสำเนานี้แตกต่างจากเปลือกย่อยอย่างไรเมื่อคุณเรียกใช้สคริปต์ด้วย. / foo.sh?
Didier A.

และเมื่อคุณเรียกใช้ pgrep โดยไม่มีการทดแทนคำสั่งฉันคิดว่ามันทำงานจากภายในเชลล์เดียวกันกับที่สคริปต์ทำงานหรือไม่ คล้ายกับการจัดหา?
Didier A.

@didibus ฉันไม่แน่ใจว่าคุณหมายถึงอะไร การทดแทนคำสั่งทำงานในเชลล์ย่อย ./foo.shรันในเชลล์ใหม่ที่ไม่ใช่สำเนาของพาเรนต์ ตัวอย่างเช่นหากคุณตั้งค่าfoo="bar"ในเทอร์มินัลของคุณแล้วเรียกใช้สคริปต์ที่ดำเนินการecho $fooคุณจะได้รับบรรทัดว่างเนื่องจากเชลล์ของสคริปต์จะไม่ได้รับค่าของตัวแปร pgrepเป็นไบนารีแยกต่างหากและใช่มันถูกเรียกใช้โดยสคริปต์ที่คุณกำลังเรียกใช้
terdon

โดยทั่วไปฉันต้องการชี้แจงใน: "ทราบว่าไม่มีการทดแทนคำสั่ง" เหตุใดการเรียกใช้ไบนารี pgrep จากสคริปต์จึงไม่เพิ่มเชลล์พิเศษ แต่การรันไบนารี binary ด้วยการแทนที่คำสั่งจะทำอย่างไร ประการที่สองฉันต้องการคำชี้แจงเกี่ยวกับ "copy of parent shell" นั่นก็เหมือนกับ sub-shell ที่ตัวแปร shell ของ parent ถูกคัดลอกไปยัง child หรือไม่? ทำไมการทดแทนคำสั่งทำเช่นนั้น?
Didier A.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.