Linux จัดการกับเชลล์สคริปต์ได้อย่างไร?


22

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

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

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


3
ลองมัน. (มันจะดำเนินต่อไป)
devnull

1
@devnull จริงๆแล้วมีคำถามที่น่าสนใจที่นี่ ได้รับไม่ว่าจะดำเนินการต่อหรือไม่ทดสอบเล็กน้อย แต่มีความแตกต่างระหว่างไฟล์ไบนารี (ซึ่งโหลดลงในหน่วยความจำ) และสคริปต์ที่มีบรรทัด Shebang หรือสคริปต์ที่ไม่มีบรรทัด Shebang
terdon

1
คุณอาจสนใจคำตอบนี้
terdon

23
เพื่อจุดประสงค์ที่แท้จริงของคุณในการลบเชลล์สคริปต์ในระหว่างการดำเนินการมันไม่สำคัญว่าจะถูกอ่านในครั้งเดียวหรือทีละบรรทัด ใน Unix ไอโหนดจะไม่ถูกลบออกจริง ๆ (แม้ว่าจะไม่มีลิงก์ไปยังไดเรกทอรีใด ๆ ก็ตาม) จนกว่าไฟล์ที่เปิดล่าสุดจะถูกปิด กล่าวอีกนัยหนึ่งแม้ว่าเชลล์ของคุณจะอ่านในบรรทัดสคริปต์เชลล์ทีละบรรทัดในระหว่างการดำเนินการก็ยังปลอดภัยที่จะลบ ข้อยกเว้นเพียงอย่างเดียวคือถ้าเชลล์ของคุณเป็นชนิดที่ปิดและเปิดเชลล์สคริปต์ใหม่ทุกครั้ง แต่ถ้าเป็นเช่นนั้นคุณจะพบปัญหา (ความปลอดภัย) ที่ใหญ่กว่ามาก
Chris Jester-Young

คำตอบ:


33

หากคุณใช้straceคุณสามารถดูวิธีการทำงานของเชลล์สคริปต์เมื่อทำงาน

ตัวอย่าง

สมมติว่าฉันมีเชลล์สคริปต์นี้

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

ใช้มันโดยใช้strace:

$ strace -s 2000 -o strace.log ./hello_ul.bash
Hello Unix & Linux!
$

การดูภายในstrace.logไฟล์จะแสดงสิ่งต่อไปนี้

...
open("./hello_ul.bash", O_RDONLY)       = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff0b6e3330) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
read(3, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 80) = 40
lseek(3, 0, SEEK_SET)                   = 0
getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
fcntl(255, F_GETFD)                     = -1 EBADF (Bad file descriptor)
dup2(3, 255)                            = 255
close(3)     
...

เมื่อไฟล์ถูกอ่านแล้วมันจะถูกเรียกใช้งาน:

...
read(255, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 40) = 40
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc0b38ba000
write(1, "Hello Unix & Linux!\n", 20)   = 20
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
read(255, "", 40)                       = 0
exit_group(0)                           = ?

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

หมายเหตุ:อย่า แต่! อ่านเพื่อทำความเข้าใจว่าทำไมคุณไม่ควรยุ่งกับไฟล์สคริปต์ที่กำลังทำงานอยู่

แล้วล่ามคนอื่นล่ะ

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

เหตุใดเราจึงไม่สามารถแก้ไขไฟล์ได้

หากคุณใช้สคริปต์ที่มีขนาดใหญ่กว่ามาก แต่คุณจะสังเกตได้ว่าการทดสอบด้านบนนั้นทำให้เข้าใจผิดเล็กน้อย ในความเป็นจริงล่ามส่วนใหญ่โหลดไฟล์ในบล็อก นี่เป็นมาตรฐานที่ค่อนข้างสวยด้วยเครื่องมือ Unix จำนวนมากที่พวกมันโหลดบล็อกของไฟล์ประมวลผลแล้วโหลดบล็อกอื่น คุณสามารถเห็นพฤติกรรมนี้ด้วยคำถาม & คำตอบ U ที่ฉันเขียนขึ้นเมื่อไม่นานมานี้เกี่ยวกับgrepหัวข้อ: grep / egrep ใช้ข้อความในปริมาณเท่าไหร่ในแต่ละครั้ง .

ตัวอย่าง

สมมติว่าเราทำเชลล์สคริปต์ต่อไปนี้

$ ( 
    echo '#!/bin/bash'; 
    for i in {1..100000}; do printf "%s\n" "echo \"$i\""; done 
  ) > ascript.bash;
$ chmod +x ascript.bash

ส่งผลให้ไฟล์นี้:

$ ll ascript.bash 
-rwxrwxr-x. 1 saml saml 1288907 Mar 23 18:59 ascript.bash

ซึ่งมีเนื้อหาประเภทต่อไปนี้:

$ head -3 ascript.bash ; echo "..."; tail -3 ascript.bash 
#!/bin/bash
echo "1"
echo "2"
...
echo "99998"
echo "99999"
echo "100000"

ตอนนี้เมื่อคุณเรียกใช้สิ่งนี้โดยใช้เทคนิคเดียวกันกับstrace:

$ strace -s 2000 -o strace_ascript.log ./ascript.bash
...    
read(255, "#!/bin/bash\necho \"1\"\necho \"2\"\necho \"3\"\necho \"4\"\necho \"5\"\necho \"6\"\necho \"7\"\necho \"8\"\necho \"9\"\necho \"10\"\necho 
...
...
\"181\"\necho \"182\"\necho \"183\"\necho \"184\"\necho \"185\"\necho \"186\"\necho \"187\"\necho \"188\"\necho \"189\"\necho \"190\"\necho \""..., 8192) = 8192

คุณจะสังเกตเห็นว่าไฟล์กำลังถูกอ่านที่เพิ่มขึ้น 8KB ดังนั้น Bash และเชลล์อื่น ๆ จะไม่โหลดไฟล์ทั้งหมด แต่จะอ่านเป็นบล็อก

อ้างอิง


@terdon - ใช่ฉันจำได้ว่าเคยเห็นคำถามและคำตอบนั้นมาก่อน
slm

5
ด้วยสคริปต์ขนาด 40 ไบต์อ่านได้ในบล็อกเดียว ลองด้วยสคริปต์> 8kB
Gilles 'หยุดความชั่วร้าย'

ฉันไม่เคยลอง แต่ฉันคิดว่าการลบไฟล์ไม่ได้ทำจริงจนกว่ากระบวนการทั้งหมดจะปิดตัวอธิบายไฟล์ที่เกี่ยวข้องกับไฟล์ที่ลบดังนั้นทุบตีอาจอ่านต่อจากไฟล์ที่ลบออก
Farid Nouri Neshat

@Gilles - ใช่ฉันเพิ่มตัวอย่างได้รับมัน
slm

2
ลักษณะการทำงานนี้ขึ้นอยู่กับรุ่น ฉันทดสอบด้วย bash เวอร์ชัน 3.2.51 (1) - คลายออกและพบว่ามันไม่ได้บัฟเฟอร์ผ่านบรรทัดปัจจุบัน (ดูคำตอบของ stackoverflow นี้ )
Gordon Davisson

11

นี่คือเชลล์ที่ต้องพึ่งพามากกว่าระบบปฏิบัติการที่ใช้

kshอ่านสคริปต์แบบออนดีมานด์ 8k หรือ 64k ไบต์ขึ้นอยู่กับรุ่น

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

สิ่งนี้มีไว้สำหรับการก่อสร้างอย่างง่าย ๆ เช่นชุดคำสั่งธรรมดา

หากใช้คำสั่งโครงสร้างเชลล์ ( ในกรณีที่คำตอบที่ยอมรับไม่ได้รับการพิจารณา ) เช่นfor/do/doneลูป, case/esacสวิทช์, เอกสารที่นี่, subshell ที่อยู่ในวงเล็บ, วงเล็บ, คำจำกัดความของฟังก์ชั่น ฯลฯ ถึงจุดสิ้นสุดของสิ่งก่อสร้างเพื่อให้แน่ใจว่าไม่มีข้อผิดพลาดทางไวยากรณ์

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

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

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


7

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

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


4

ไฟล์ 'x':

cat<<'dog' >xyzzy
LANG=C
T=`tty`
( sleep 2 ; ls -l xyzzy >$T ) &
( sleep 4 ; rm -v xyzzy >$T ) &
( sleep 4 ; ls -l xyzzy >$T ) &
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
dog

sh xyzzy

วิ่ง:

~/wrk/tmp$ sh x
alive.
alive.
alive.
-rw-r--r-- 1 yeti yeti 287 Mar 23 16:57 xyzzy
alive.
removed `xyzzy'
ls: cannot access xyzzy: No such file or directory
alive.
alive.
alive.
alive.
~/wrk/tmp$ _

IIRC ไฟล์จะไม่ถูกลบตราบใดที่กระบวนการยังคงเปิดอยู่ การลบเพียงแค่ลบ DIRENT ที่ระบุ

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