มีแนว shebang (การประกาศสคริปต์) มากเกินไป - วิธีใดที่จะลดจำนวนลงบ้าง


12

ฉันมีโครงการประกอบด้วย.shไฟล์ขนาดเล็กประมาณ 20 ไฟล์ ฉันตั้งชื่อ "เล็ก" เหล่านี้เพราะโดยทั่วไปไม่มีไฟล์ใดที่มีโค้ดเกิน 20 บรรทัด ฉันใช้แนวทางแบบแยกส่วนเพราะฉันภักดีต่อปรัชญา Unixและง่ายต่อการดูแลรักษาโครงการ

ในการเริ่มต้นของแต่ละไฟล์ผมใส่.sh#!/bin/bash

ฉันเข้าใจว่าการประกาศสคริปต์มีจุดประสงค์สองประการ:

  1. พวกเขาช่วยให้ผู้ใช้เรียกคืนสิ่งที่เชลล์ต้องการเพื่อเรียกใช้ไฟล์ (พูดหลังจากผ่านไปหลายปีโดยไม่ต้องใช้ไฟล์)
  2. พวกเขาให้แน่ใจว่าสคริปต์ทำงานเฉพาะกับเปลือกบางอย่าง (ทุบตีในกรณีนั้น) เพื่อป้องกันพฤติกรรมที่ไม่คาดคิดในกรณีที่ใช้เปลือกอื่น

เมื่อโครงการเริ่มเติบโตจากไฟล์ 5 ไฟล์เป็น 20 ไฟล์หรือจาก 20 ไฟล์เป็น 50 ไฟล์ (ไม่ใช่กรณีนี้ แต่เพื่อสาธิต) เรามีการประกาศสคริปต์20 บรรทัดหรือ 50 บรรทัด ฉันยอมรับแม้ว่ามันอาจจะตลกสำหรับบางคน แต่ก็รู้สึกซ้ำซ้อนสำหรับฉันที่จะใช้ 20 หรือ 50 แทนที่จะพูดเพียง1 ต่อโครงการ (อาจเป็นไฟล์หลักของโครงการ)

มีวิธีที่จะหลีกเลี่ยงความซ้ำซ้อนที่ถูกกล่าวหานี้ 20 หรือ 50 หรือจำนวนบรรทัดของการประกาศสคริปต์มากขึ้นโดยใช้การประกาศสคริปต์ "ทั่วโลก" ในไฟล์หลักบางไฟล์?


2
คุณไม่ควร แต่สามารถ; ลบออกและรันสคริปต์ทั้งหมดของคุณด้วย/bin/bash -x "$script"
jesse_b


... โดยปกติบางสิ่งบางอย่างมาถึงจุดนี้ฉันได้ข้อสรุปแล้วถึงเวลาที่จะหยุดการใช้เชลล์สคริปต์และรับภาษาสคริปต์ที่แท้จริงในการทำงาน
Shadur

คำตอบ:


19

แม้ว่าตอนนี้โครงการของคุณอาจจะประกอบด้วยสคริปต์ 50 Bash เพียงอย่างเดียว แต่ก็จะเริ่มสะสมสคริปต์ที่เขียนในภาษาอื่นเช่น Perl หรือ Python ไม่ช้าก็เร็ว (เพื่อประโยชน์ที่ภาษาสคริปต์เหล่านี้มีซึ่ง Bash ไม่มี)

หากไม่มี#!บรรทัดที่เหมาะสมในแต่ละสคริปต์มันจะยากอย่างยิ่งที่จะใช้สคริปต์ต่าง ๆ โดยไม่ต้องรู้ว่าจะใช้ล่ามอะไร ไม่สำคัญว่าสคริปต์ทุกตัวจะถูกเรียกใช้จากสคริปต์อื่น ๆเพียงแค่ย้ายความยากลำบากจากผู้ใช้ไปยังนักพัฒนาเท่านั้น ทั้งสองกลุ่มนี้ไม่จำเป็นต้องรู้ภาษาที่เขียนด้วยสคริปต์เพื่อให้สามารถใช้งานได้

เชลล์สคริปต์ดำเนินการโดยไม่มี#!บรรทัดและไม่มีล่ามที่ชัดเจนจะดำเนินการในรูปแบบที่แตกต่างกันขึ้นอยู่กับสิ่งที่เชลล์เรียกพวกเขา (ดูตัวอย่างเช่นคำถามล่ามเชลล์คนใดที่เรียกใช้สคริปต์ที่ไม่มี shebangและโดยเฉพาะคำตอบของStéphane ) ในสภาพแวดล้อมการผลิต (คุณต้องการพฤติกรรมที่สอดคล้องและอาจพกพาได้)

สคริปต์ที่ดำเนินการโดยล่ามที่ชัดเจนจะถูกเรียกใช้โดยล่ามนั้นโดยไม่คำนึงถึงสิ่งที่#!-line พูด สิ่งนี้จะทำให้เกิดปัญหาขึ้นอีกหากคุณตัดสินใจที่จะนำสคริปต์ Bash ไปใช้ใน Python หรือภาษาอื่น ๆ

คุณควรใช้การกดแป้นพิเศษเหล่านั้นและเพิ่ม#!-line ลงในสคริปต์แต่ละบทและทุกบท


ในบางสภาพแวดล้อมมีข้อความทางกฎหมายหลายฉบับในแต่ละสคริปต์ในแต่ละโครงการ มีความสุขมากที่เป็นเพียง#!บรรทัดที่รู้สึก "ซ้ำซ้อน" ในโครงการของคุณ


คำตอบควรถูกขยายโดยสิ่งนี้: เชลล์กำหนดตัวแปลโดย#!บรรทัดทันทีที่ไฟล์มีสิทธิใช้งาน exectuable และไม่ได้โหลดโดย interpreter แต่โดยเชลล์ นั่นคือ/path/to/myprog.plเรียกใช้โปรแกรม perl หากมี#!บรรทัด (ชี้ไปที่ตัวแปลภาษา perl) และไฟล์นั้นได้รับอนุญาตจาก exec
rexkogitans

11

#!คุณเข้าใจผิดจุด จริงๆแล้วมันเป็นคำสั่งให้ระบบปฏิบัติการเรียกใช้โปรแกรมนี้ภายใต้ล่ามที่กำหนดไว้

ดังนั้นสคริปต์อาจเป็นได้#!/usr/bin/perlและโปรแกรมที่เป็นผลลัพธ์สามารถรันได้./myprogramและสามารถใช้ล่ามภาษา Perl ได้ ที่คล้ายกัน#!/usr/bin/pythonจะทำให้โปรแกรมทำงานภายใต้หลาม

ดังนั้นบรรทัด#!/bin/bashบอกให้ OS รันโปรแกรมนี้ภายใต้ bash

นี่คือตัวอย่าง:

$ echo $0
/bin/ksh

$ cat x
#!/bin/bash

ps -aux | grep $$

$ ./x
sweh      2148  0.0  0.0   9516  1112 pts/5    S+   07:58   0:00 /bin/bash ./x
sweh      2150  0.0  0.0   9048   668 pts/5    S+   07:58   0:00 grep 2148

ดังนั้นแม้ว่าเชลล์ของฉันจะเป็น ksh แต่โปรแกรม "x" ทำงานภายใต้การทุบตีเนื่องจาก#!บรรทัดและเราสามารถเห็นได้อย่างชัดเจนในรายการกระบวนการ


ps -auxอาจให้คำเตือน
Weijun Zhou

@WeijunZhou พูดมาก ...
Kusalananda

รุ่นบางส่วนของให้เตือนps Warning: bad syntax, perhaps a bogus '-'?
Weijun Zhou

2
pssuooprts ทั้งไวยากรณ์อาร์กิวเมนต์ BSDและUXIX -auxเป็นสิ่งที่ผิด UNIX สตีเฟ่นอาจจะหมายถึงauxซึ่งเป็นที่ถูกต้อง BSD
Jasen

1
คน 2 สิ่ง; (1) มุ่งเน้นไปที่แนวคิดตัวอย่าง (ที่psแสดงให้เห็นถึงการเรียกbash) ไม่ใช่ไวยากรณ์ที่ชัดเจน; (2) ps -auxทำงานได้อย่างสมบูรณ์บน CentOS 7 และ Debian 9 โดยไม่มีการเตือน cut'n'paste นั้นเป็นผลลัพธ์จากเครื่องของฉัน การอภิปรายทั้งหมดของถ้า-จำเป็นหรือไม่สามารถใช้ได้กับlinux procps รุ่นเก่าไม่ใช่เวอร์ชันปัจจุบัน
สตีเฟ่นแฮร์ริส

9

บน .sh

สิ่งที่ไม่ดีคือ.shในตอนท้ายของชื่อไฟล์

ลองนึกภาพว่าคุณเขียนสคริปต์ตัวใดตัวหนึ่งในงูหลาม

ทีนี้คุณต้องเปลี่ยนบรรทัดแรกให้#!/usr/bin/python3เป็นแบบนี้ก็ไม่ได้แย่เหมือนกันเพราะคุณต้องเปลี่ยนรหัสบรรทัดอื่น ๆ ในไฟล์ด้วย แต่คุณยังมีการเปลี่ยนชื่อไฟล์จากไปprog.sh prog.pyจากนั้นคุณต้องค้นหาทุกหนทุกแห่งในสคริปต์อื่นและสคริปต์ผู้ใช้ทั้งหมดของคุณ (สคริปต์ที่คุณไม่เคยรู้มาก่อน) และเปลี่ยนให้ใช้สคริปต์ใหม่ sed -e 's/.sh/.py/g'อาจช่วยคนที่คุณรู้จัก แต่ตอนนี้เครื่องมือควบคุมการแก้ไขของคุณกำลังแสดงการเปลี่ยนแปลงในไฟล์ที่ไม่เกี่ยวข้องจำนวนมาก

ผลัดกันทำวิธี Unix และชื่อโปรแกรมไม่ได้progprog.sh

บน #!

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

chmod +x prog #on gnu adds execute permission to prog.
./prog #runs the program.

สำหรับระบบที่ไม่ใช่ gnu ให้อ่านคู่มือสำหรับchmod(และเรียนรู้เลขฐานแปด) อาจอ่านได้เลย


1
อืมส่วนขยายไม่จำเป็นต้องแย่อย่างน้อยพวกเขาจะช่วยสื่อสารกับผู้ใช้คนอื่น ๆ ว่าเป็นไฟล์ประเภทใด
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy แต่คุณไม่จำเป็นต้องรู้ภาษาคุณเพียงแค่เรียกใช้ แม้แต่ไมโครซอฟท์ก็พยายามที่จะขยับออกห่างจากส่วนขยาย อย่างไรก็ตามฉันยอมรับว่าพวกเขาอาจเป็นความคิดที่ดี.imageสำหรับรูปภาพ .audioสำหรับเสียง ฯลฯ ดังนั้นบอกคุณว่ามันคืออะไร แต่ไม่เกี่ยวกับการใช้งาน / การเข้ารหัส
ctrl-alt-delor

1
@ Ctrl-Alt-Delor หากไฟล์ที่เรียกว่าlaunch-missilesหรือdelete-project-and-make-manager-pissedฉันไม่อยาก "เพียงแค่เรียกมันว่า" เมื่อฉันเป็นเพียงอยากรู้เกี่ยวกับภาษา :)
Sergiy Kolodyazhnyy

2
หากคุณอยากรู้ภาษาใช้fileคำสั่ง มันรู้จักประเภทไฟล์ส่วนใหญ่
Jasen

2
ฉันยอมรับว่าส่วนขยายไม่ดีสำหรับสคริปต์ที่เรียกใช้งานได้ด้วยเหตุผลที่กำหนดฉันใช้ส่วนขยาย. sh สำหรับสคริปต์ที่ต้องมีที่มาจากเชลล์สคริปต์อื่น (เช่นสคริปต์ที่ไม่สามารถเรียกทำงานได้) - ในสถานการณ์นั้นส่วนต่อท้ายคือ สำคัญ / ถูกต้องเพราะมันบอกเราว่าเราสามารถใช้ไฟล์ได้อย่างไร
ลอเรนซ์ Renshaw

8

[บรรทัด hashbang]ตรวจสอบให้แน่ใจว่าสคริปต์ทำงานเฉพาะกับเชลล์ (Bash ในกรณีนั้น) เพื่อป้องกันพฤติกรรมที่ไม่คาดคิดในกรณีที่ใช้เชลล์อื่น

มันอาจจะคุ้มค่าที่ชี้ให้เห็นว่าสิ่งนี้ค่อนข้างผิด บรรทัด hashbang นั้นป้องกันไม่ให้คุณพยายามป้อนไฟล์ไปยัง shell / interpreter ที่ไม่ถูกต้อง:

$ cat array.sh
#!/bin/bash
a=(a b c)
echo ${a[1]} $BASH_VERSION
$ dash array.sh
array.sh: 2: array.sh: Syntax error: "(" unexpected

(หรือคล้ายกันกับawk -f array.shฯลฯ )


แต่ในกรณีใด ๆ อีกวิธีหนึ่งในการแยกส่วนจะเป็นการกำหนดฟังก์ชั่นของเชลล์ในไฟล์หนึ่งฟังก์ชันต่อไฟล์ถ้าคุณชอบแล้วsourceไฟล์จากโปรแกรมหลัก ด้วยวิธีนี้คุณไม่จำเป็นต้องใช้ hashbangs: พวกเขาจะถูกมองว่าเป็นความคิดเห็นอื่น ๆ และทุกอย่างจะทำงานโดยใช้เชลล์ตัวเดียวกัน ข้อเสียคือคุณจะต้องsourceใช้ไฟล์ที่มีฟังก์ชั่น (เช่นfor x in functions/*.src ; do source "$x" ; done) และพวกมันทำงานโดยใช้เชลล์ตัวเดียวกันดังนั้นคุณจึงไม่สามารถแทนที่หนึ่งในนั้นด้วย Perl-Implement ได้โดยตรง


+1 ตัวอย่างเช่นกับอาร์เรย์ทุบตี ผู้ใช้ที่ไม่คุ้นเคยกับหลายเชลล์หรือบางอย่างที่ POSIX กำหนดอาจถือว่าคุณสมบัติบางอย่างของเชลล์หนึ่งมีอยู่ในอีกเชลล์หนึ่ง นอกจากนี้สำหรับฟังก์ชั่นอาจเกี่ยวข้องกัน: unix.stackexchange.com/q/313256/85039
Sergiy Kolodyazhnyy

4

มีวิธีที่จะหลีกเลี่ยงความซ้ำซ้อนที่ถูกกล่าวหานี้ 20 หรือ 50 หรือจำนวนบรรทัดของการประกาศสคริปต์มากขึ้นโดยใช้การประกาศสคริปต์ "ทั่วโลก" ในไฟล์หลักบางไฟล์?

มี - มันถูกเรียกว่าพกพาหรือ POSIX มุ่งมั่นที่จะเขียนสคริปต์ในแบบพกพาวิธีที่เป็นกลางเชลล์หาแหล่งมาจากสคริปต์ที่ใช้#!/bin/shหรือเมื่อคุณใช้/bin/shแบบโต้ตอบ (หรืออย่างน้อยเชลล์ที่สอดคล้องกับ POSIX เช่นksh)

อย่างไรก็ตามสคริปต์แบบพกพาไม่ได้ช่วยให้คุณประหยัดจาก:

  • จำเป็นต้องใช้คุณสมบัติของหนึ่งในหอย ( คำตอบของ ilkkachuเป็นตัวอย่าง)
  • จำเป็นต้องใช้ภาษาสคริปต์ขั้นสูงกว่าเชลล์ (Python และ Perl)
  • ข้อผิดพลาด PEBKAC: สคริปต์ของคุณตกอยู่ในมือของ J.Doe ผู้ใช้ที่เรียกสคริปต์ของคุณไม่เป็นคุณตั้งใจให้มันตัวอย่างเช่นพวกเขาทำงานสคริปต์ด้วย/bin/shcsh

หากฉันอาจแสดงความคิดเห็นของฉัน "หลีกเลี่ยงความซ้ำซ้อน" โดยการกำจัด#!เป็นเป้าหมายที่ผิด เป้าหมายควรจะสอดคล้องประสิทธิภาพและความจริงสคริปต์การทำงานว่างานและพิจารณากรณีมุมตามกฎทั่วไปบางอย่างเช่น-LS ไม่แยกหรือเสมอ quoting ตัวแปรเว้นแต่-จำเป็นคำแยก

พวกเขาช่วยให้ผู้ใช้เรียกคืนสิ่งที่เชลล์ต้องการเพื่อเรียกใช้ไฟล์ (พูดหลังจากผ่านไปหลายปีโดยไม่ต้องใช้ไฟล์)

พวกเขาไม่ได้มีไว้สำหรับผู้ใช้ - เป็นระบบปฏิบัติการสำหรับใช้ล่ามที่เหมาะสม "เหมาะสม" ในกรณีนี้หมายถึงระบบปฏิบัติการพบสิ่งที่อยู่ใน shebang ถ้ารหัสด้านล่างสำหรับเปลือกที่ผิด - นั่นคือความผิดของผู้เขียน สำหรับหน่วยความจำผู้ใช้ฉันไม่คิดว่า "ผู้ใช้" ของโปรแกรมเช่น Microsoft Word หรือ Google Chrome เคยต้องการทราบว่ามีการเขียนโปรแกรมภาษาอะไร ผู้เขียนอาจต้องการ

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

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

พวกเขาทำไม่ได้ สิ่งที่ป้องกันพฤติกรรมที่ไม่คาดคิดกำลังเขียนสคริปต์แบบพกพา


1

เหตุผลที่คุณต้องใส่#!/bin/bashที่ด้านบนของแต่ละสคริปต์ก็คือคุณใช้มันเป็นกระบวนการแยกต่างหาก

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

หากคุณต้องการลบ#!บรรทัดตัวเลือกของคุณคือ:

  1. ดำเนินการสคริปต์โดยตรง (เช่นที่คุณทำอยู่ตอนนี้) และ หวังว่าล่ามเริ่มต้นจะตรงกับสคริปต์ - คุณอาจปลอดภัยในระบบ Linux ส่วนใหญ่ แต่ไม่สามารถพกพาไปยังระบบอื่น ๆ ได้ (เช่น Solaris) และสามารถเปลี่ยนเป็น อะไรก็ได้ (ฉันสามารถตั้งให้มันทำงานvimบนไฟล์ได้!)

  2. bash myscript.shรันสคริปต์โดยใช้ ใช้งานได้ดี แต่คุณต้องระบุเส้นทางไปยังสคริปต์และมันยุ่ง

  3. แหล่งที่มาของสคริปต์ที่ใช้. myscript.shซึ่งไหลสคริปต์ภายในกระบวนการล่ามปัจจุบัน (เช่นไม่เป็นกระบวนการย่อย) แต่สิ่งนี้จะต้องมีการปรับเปลี่ยนสคริปต์ของคุณและทำให้พวกเขาสามารถแยกส่วน / นำกลับมาใช้ซ้ำได้

ในกรณีที่ 2 และ 3 ไฟล์ไม่ต้องการ (และไม่ควรมี) ดำเนินการการอนุญาตและให้คำต่อท้าย.sh(หรือ.bash) เป็นความคิดที่ดี

แต่ไม่มีตัวเลือกเหล่านี้ที่ดูดีสำหรับโครงการของคุณอย่างที่คุณบอกว่าคุณต้องการให้สคริปต์ของคุณเป็นแบบแยกส่วน (=> อิสระและอยู่ในตัวเอง) ดังนั้นคุณควรให้#!/bin/bashบรรทัดของคุณอยู่ด้านบนสุดของสคริปต์

ในคำถามของคุณคุณบอกว่าคุณกำลังติดตามปรัชญาของ Unix ถ้ามันเป็นเรื่องจริงคุณควรจะเรียนรู้ว่า#!บรรทัดนั้นมีไว้เพื่ออะไรและใช้มันในทุก ๆ สคริปต์ที่ปฏิบัติการได้!


ขอบคุณสำหรับคำตอบที่ดี ฉันรักทุกอย่างในคำตอบและความคิดของ upvoting then you should learn what the #! line is really for, and keep using it in every executable script!จนกว่านี้: หนึ่งสามารถติดตาม U.Philosophy ขึ้นจุดที่แน่นอนของความรู้ หลังจากคำตอบทั้งหมดเหล่านี้ฉันเข้าใจว่าเหตุใดจึงสามารถรวมไว้ในคำนั้น ฉันขอแนะนำให้แก้ไขคำตอบแทนที่ด้วย "ดู shebang เป็นส่วนภายในของ Unix Philosophy ที่คุณเคารพและยอมรับ"
9303970

1
ขออภัยถ้าฉันถูกโดยตรงหรือถ้าความคิดเห็นที่ดูเหมือนหยาบคาย ความคิดเห็นของคุณแนะนำว่าคุณต้องการทำงานใน IDE ด้วย 'โครงการ' ที่ช่วยให้คุณกำหนดกฎสากลสำหรับสคริปต์ภายในโครงการนั้น นั่นเป็นวิธีที่ถูกต้องในการพัฒนา แต่มันไม่เหมาะกับการใช้สคริปต์แต่ละตัวในฐานะโปรแกรมอิสระเดี่ยว (ปรัชญา Unix 'ที่คุณอ้างถึง)
Laurence Renshaw
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.