ทำไม "ps axe" ไม่พบสคริปต์ทุบตีที่รันอยู่โดยไม่มี“ #!” หัวข้อ?


13

เมื่อฉันเรียกใช้สคริปต์นี้ตั้งใจให้ทำงานจนกว่าจะฆ่า ...

# foo.sh

while true; do sleep 1; done

... ฉันไม่สามารถหาได้โดยใช้ps ax:

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

... แต่ถ้าฉันเพิ่งเพิ่ม#!ส่วนหัว " " ทั่วไปลงในสคริปต์ ...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

... จากนั้นสคริปต์จะสามารถค้นหาได้โดยpsคำสั่งเดียวกัน...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

ทำไมเป็นเช่นนี้
นี่อาจเป็นคำถามที่เกี่ยวข้อง: ฉันคิดว่า " #" เป็นเพียงคำนำหน้าความคิดเห็นและถ้าเป็นเช่นนั้น " #! /usr/bin/bash" ก็ไม่ได้เป็นอะไรมากไปกว่าความคิดเห็น แต่ " #!" มีความสำคัญมากกว่าความคิดเห็นหรือไม่?


คุณใช้ Unix อะไร?
Kusalananda

@Kusalananda - Linux linuxbox 3.11.10-301.fc20.x86_64 # 1 SMP พฤ. 5 ธ.ค. 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

คำตอบ:


13

เมื่อเปลือกโต้ตอบปัจจุบันคือbashและคุณเรียกใช้สคริปต์โดยไม่ต้อง#!บรรทัดแล้วbashจะเรียกใช้สคริปต์ กระบวนการนี้จะปรากฏขึ้นในการส่งออกเป็นเพียงแค่ps axbash

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

ในเทอร์มินัลอื่น:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

ที่เกี่ยวข้อง:


ส่วนที่เกี่ยวข้องจัดทำbashคู่มือ:

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

หากโปรแกรมนั้นเป็นไฟล์ที่เริ่มต้นด้วย#!ส่วนที่เหลือของบรรทัดแรกจะระบุล่ามสำหรับโปรแกรม เชลล์เรียกใช้งานล่ามที่ระบุบนระบบปฏิบัติการที่ไม่จัดการรูปแบบที่สามารถดำเนินการได้นี้เอง [ ... ]

ซึ่งหมายความว่าการรัน./foo.shบนบรรทัดคำสั่งเมื่อfoo.shไม่มี-line #!จะเหมือนกับการรันคำสั่งในไฟล์ใน subshell เช่น

$ ( echo "$BASHPID"; while true; do sleep 1; done )

ด้วยเส้นที่เหมาะสม#!ชี้ไปที่เช่น/bin/bashมันเป็นเช่นนั้น

$ /bin/bash foo.sh

ฉันคิดว่าฉันทำตาม แต่สิ่งที่คุณพูดยังเป็นจริงในกรณีที่สอง: ทุบตียังเรียกใช้สคริปต์ในกรณีที่สองตามที่สามารถสังเกตได้เมื่อpsแสดงสคริปต์ทำงานเป็น " /usr/bin/bash ./foo.sh" ดังนั้นในกรณีแรกอย่างที่คุณพูด bash จะเรียกใช้สคริปต์ แต่สคริปต์นั้นไม่จำเป็นต้อง "ส่งผ่าน" ไปยังไฟล์ปฏิบัติการ bash ที่แยกย่อยเช่นเดียวกับกรณีที่สองหรือไม่ (และถ้าเป็นเช่นนั้นฉันคิดว่ามันจะหาได้กับท่อเพื่อ grep ... ?)
StoneThrow

@StoneThrow ดูคำตอบที่ปรับปรุงแล้ว
Kusalananda

"... ยกเว้นว่าคุณได้รับกระบวนการใหม่" - ดีคุณจะได้รับกระบวนการใหม่ด้วยวิธีใดวิธีหนึ่งยกเว้นว่าจะ$$ยังคงชี้ไปที่กระบวนการเก่าในเคสย่อย ( echo $BASHPID/ bash -c 'echo $PPID')
Michael Homer

@MichaelHomer อ่าขอบคุณมากสำหรับสิ่งนั้น! จะอัปเดต
Kusalananda

12

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

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

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

กลไก shebang ช่วยให้เคอร์เนลเลื่อนงานของการตีความรหัสไปยังโปรแกรมอื่น เมื่อเคอร์เนลเห็นว่าไฟล์เรียกทำงานเริ่มต้นด้วย#!มันจะอ่านตัวละครสองสามตัวถัดไปและตีความบรรทัดแรกของไฟล์ (ลบด้วย#!พื้นที่ว่างนำหน้าและทางเลือก) เป็นพา ธ ไปยังไฟล์อื่น (บวกอาร์กิวเมนต์ที่ฉันจะไม่พูดถึงที่นี่ ) เมื่อเคอร์เนลจะบอกให้เรียกใช้ไฟล์/my/scriptและจะเห็นว่าไฟล์เริ่มต้นด้วยสาย#!/some/interpreterเคอร์เนลดำเนินการกับการโต้แย้ง/some/interpreter /my/scriptมันขึ้นอยู่กับ/some/interpreterการตัดสินใจว่า/my/scriptเป็นไฟล์สคริปต์ที่ควรดำเนินการ

จะเกิดอะไรขึ้นถ้าไฟล์ไม่มีรหัสเนทีฟในรูปแบบที่เคอร์เนลเข้าใจและไม่เริ่มต้นด้วย shebang ถ้าอย่างนั้นไฟล์ไม่สามารถเรียกใช้งานได้และการexecveเรียกระบบล้มเหลวพร้อมรหัสENOEXECข้อผิดพลาด

นี่อาจเป็นจุดสิ้นสุดของเรื่องราว แต่กระสุนส่วนใหญ่ใช้คุณสมบัติทางเลือก หากเคอร์เนลส่งคืนENOEXECเชลล์จะพิจารณาเนื้อหาของไฟล์และตรวจสอบว่าดูเหมือนเชลล์สคริปต์หรือไม่ หากเชลล์คิดว่าไฟล์ดูเหมือนว่าเชลล์สคริปต์จะดำเนินการด้วยตัวเอง รายละเอียดของสิ่งนี้มันขึ้นอยู่กับเปลือก คุณสามารถเห็นสิ่งที่เกิดขึ้นโดยการเพิ่มps $$สคริปต์ของคุณและอื่น ๆ โดยการดูกระบวนการstrace -p1234 -f -eprocessที่ 1234 เป็น PID ของเชลล์

ในทุบตีกลไกทางเลือกนี้จะดำเนินการโดยการเรียกแต่ไม่fork execveกระบวนการทุบตีเด็กล้างสถานะภายในของตัวเองและเปิดไฟล์สคริปต์ใหม่เพื่อเรียกใช้ ดังนั้นกระบวนการที่เรียกใช้สคริปต์ยังคงใช้อิมเมจรหัส bash ดั้งเดิมและอาร์กิวเมนต์บรรทัดคำสั่งดั้งเดิมที่ส่งผ่านเมื่อคุณเรียกใช้งาน bash ในตอนแรก ATT ksh ทำงานในลักษณะเดียวกัน

% bash --norc
bash-4.3$ ./foo.sh 
  PID TTY      STAT   TIME COMMAND
21913 pts/2    S+     0:00 bash --norc

ในทางตรงกันข้าม Dash ตอบสนองENOEXECโดยการเรียก/bin/shด้วยพา ธ ไปยังสคริปต์ที่ส่งเป็นอาร์กิวเมนต์ ในคำอื่น ๆ เมื่อคุณรันสคริปต์ shebangless จากประมันจะทำงานเช่นถ้าสคริปต์ที่มีเส้น shebang #!/bin/shกับ Mksh และ zsh ทำงานในลักษณะเดียวกัน

% dash
$ ./foo.sh
  PID TTY      STAT   TIME COMMAND
21427 pts/2    S+     0:00 /bin/sh ./foo.sh

คำตอบที่ยอดเยี่ยม หนึ่งคำถาม RE: การนำไปใช้ทางเลือกที่คุณอธิบาย: ฉันคิดว่าตั้งแต่เด็กbashถูกแยกมันมีการเข้าถึงargv[]อาเรย์เดียวกับแม่ของมันซึ่งเป็นวิธีที่มันรู้ "อาร์กิวเมนต์บรรทัดคำสั่งเดิมที่ส่งผ่านเมื่อคุณเรียกใช้ทุบตีเดิม" และถ้า ดังนั้นนี่คือสาเหตุที่เด็กไม่ได้ผ่านสคริปต์ต้นฉบับเป็นอาร์กิวเมนต์ชัดเจน (ดังนั้นทำไม grep จึงหาไม่พบ) - มีความถูกต้องหรือไม่
StoneThrow

1
คุณสามารถปิดการทำงานของเคอร์เนล shebang ได้ ( BINFMT_SCRIPTโมดูลควบคุมสิ่งนี้และสามารถลบออก / ทำให้เป็นโมดูลแม้ว่าโดยปกติแล้วมันจะเชื่อมโยงกับเคอร์เนลแบบคงที่) แต่ฉันไม่เห็นว่าทำไมคุณถึงต้องการยกเว้นในระบบฝังตัว . เพื่อเป็นการแก้ปัญหาสำหรับความเป็นไปได้นี้bashมีการตั้งค่าสถานะ ( HAVE_HASH_BANG_EXEC) เพื่อชดเชย!
ErikF

2
@StoneThrow ไม่มากที่เด็กทุบตี "รู้ข้อโต้แย้งบรรทัดคำสั่งเดิม" เพราะมันไม่ได้แก้ไข โปรแกรมสามารถแก้ไขสิ่งที่psรายงานว่าเป็นอาร์กิวเมนต์บรรทัดคำสั่ง แต่จนถึงจุด: มันมีการปรับเปลี่ยนบัฟเฟอร์หน่วยความจำที่มีอยู่มันไม่สามารถขยายบัฟเฟอร์นี้ ดังนั้นหากทุบตีพยายามแก้ไขargvเพื่อเพิ่มชื่อของสคริปต์มันจะไม่ทำงาน เด็กไม่ได้“ ผ่านการโต้แย้ง” เพราะไม่มีการexecveเรียกระบบในเด็ก มันเป็นเพียงรูปกระบวนการทุบตีเดียวกับที่ยังคงทำงานอยู่
Gilles 'ดังนั้นหยุดความชั่วร้าย'

-1

ในกรณีแรกสคริปต์จะรันโดยชายด์แยกจากเชลล์ปัจจุบันของคุณ

คุณควรเรียกใช้ก่อนecho $$แล้วจึงดูที่เชลล์ที่มี id กระบวนการของเชลล์ของคุณเป็น id กระบวนการหลัก

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