Quá nhiều dòng shebang (khai báo tập lệnh) - có cách nào để giảm số lượng của chúng không?


12

Tôi có một dự án bao gồm khoảng 20 .shtệp nhỏ . Tôi đặt tên cho những "nhỏ" này bởi vì nói chung, không có tệp nào có hơn 20 dòng mã. Tôi đã thực hiện một cách tiếp cận theo mô-đun vì do đó tôi trung thành với triết lý Unix và tôi dễ dàng duy trì dự án hơn.

Vào đầu mỗi .shtập tin, tôi đặt #!/bin/bash.

Nói một cách đơn giản, tôi hiểu khai báo tập lệnh có hai mục đích:

  1. Chúng giúp người dùng nhớ lại shell nào cần thiết để thực thi tệp (giả sử, sau một số năm không sử dụng tệp).
  2. Họ đảm bảo rằng tập lệnh chỉ chạy với một trình bao nhất định (Bash trong trường hợp đó) để ngăn chặn hành vi không mong muốn trong trường hợp một trình bao khác được sử dụng.

Khi một dự án bắt đầu phát triển từ nói 5 tệp thành 20 tệp hoặc từ 20 tệp lên 50 tệp (không phải trường hợp này mà chỉ để chứng minh), chúng tôi có 20 dòng hoặc 50 dòng khai báo tập lệnh. Tôi thừa nhận, mặc dù có thể buồn cười với một số người, tôi cảm thấy hơi dư thừa khi sử dụng 20 hoặc 50 thay vì chỉ nói 1 cho mỗi dự án (có thể trong tệp chính của dự án).

Có cách nào để tránh sự dư thừa bị cáo buộc này là 20 hoặc 50 hoặc số lượng khai báo tập lệnh lớn hơn nhiều bằng cách sử dụng một số khai báo tập lệnh "toàn cầu", trong một số tệp chính không?


2
Bạn không nên nhưng có thể; xóa nó và thực hiện tất cả các tập lệnh của bạn với/bin/bash -x "$script"
jesse_b


... Thông thường vào thời điểm nào đó, đến thời điểm này tôi đã kết luận rằng đã đến lúc ngừng sử dụng các tập lệnh shell và lấy một ngôn ngữ kịch bản thực tế để thực hiện công việc.
Shadur

Câu trả lời:


19

Mặc dù dự án của bạn bây giờ có thể chỉ bao gồm 50 tập lệnh Bash, nhưng sớm muộn nó cũng sẽ bắt đầu tích lũy các tập lệnh được viết bằng các ngôn ngữ khác như Perl hoặc Python (vì những lợi ích mà các ngôn ngữ kịch bản này có mà Bash không có).

Nếu không có một #!dòng phù hợp trong mỗi tập lệnh, sẽ cực kỳ khó sử dụng các tập lệnh khác nhau mà không biết nên sử dụng trình thông dịch nào. Sẽ không có vấn đề gì nếu mỗi tập lệnh đơn lẻ được thực thi từ các tập lệnh khác , điều này chỉ chuyển khó khăn từ người dùng cuối sang nhà phát triển. Cả hai nhóm người này đều không cần biết kịch bản được viết bằng ngôn ngữ nào để có thể sử dụng ngôn ngữ đó.

Các tập lệnh Shell được thực thi mà không có #!dòng và không có trình thông dịch rõ ràng được thực thi theo các cách khác nhau tùy thuộc vào trình bao nào gọi chúng (xem ví dụ câu hỏi Trình thông dịch shell nào chạy tập lệnh không có shebang? Và đặc biệt là câu trả lời của Stéphane ), đó không phải là điều bạn muốn trong một môi trường sản xuất (bạn muốn có hành vi nhất quán và thậm chí có thể là tính di động).

Các tập lệnh được thực thi với một trình thông dịch rõ ràng sẽ được điều hành bởi trình thông dịch đó bất kể #!dòng đó nói gì. Điều này sẽ gây ra vấn đề tiếp theo nếu bạn quyết định triển khai lại, giả sử, một tập lệnh Bash bằng Python hoặc bất kỳ ngôn ngữ nào khác.

Bạn nên dành những tổ hợp phím bổ sung đó và luôn luôn thêm một #!dòng cho mỗi tập lệnh.


Trong một số môi trường, có các văn bản pháp lý soạn sẵn nhiều đoạn trong mỗi tập lệnh trong mỗi dự án. Hãy rất vui vì đó chỉ là một #!dòng cảm thấy "dư thừa" trong dự án của bạn.


Câu trả lời nên được mở rộng bằng cách này: Shell xác định trình thông dịch theo #!dòng ngay khi tệp có sự cho phép rõ ràng và không được tải bởi trình thông dịch, mà bằng trình bao. Đó là, /path/to/myprog.plchạy một chương trình perl nếu có #!dòng (chỉ vào trình thông dịch perl) và tệp có quyền thực thi.
rexkogitans

11

Bạn hiểu sai ý của #! . Đây thực sự là một chỉ thị cho hệ điều hành để chạy chương trình này theo trình thông dịch được xác định.

Vì vậy, một kịch bản có thể được #!/usr/bin/perl và chương trình kết quả có thể được chạy ./myprogramvà trình thông dịch perl có thể được sử dụng. Tương tự #!/usr/bin/pythonsẽ gây ra một chương trình chạy theo python.

Vì vậy, dòng #!/bin/bash cho hệ điều hành chạy chương trình này dưới bash.

Đây là một ví dụ:

$ 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

Vì vậy, mặc dù shell của tôi là ksh, chương trình "x" chạy dưới bash vì #!dòng và chúng ta có thể thấy rõ điều đó trong danh sách quy trình.


ps -auxcó thể đưa ra cảnh báo,
Weijun Zhou

@ WeijunZhou Nói thêm ...
Kusalananda

Một số phiên bản psđưa ra cảnh báo Warning: bad syntax, perhaps a bogus '-'?.
Weijun Zhou

2
pssuooprts cả cú pháp đối số BSDUXIX . -auxlà sai UNIX Stephen có lẽ có nghĩa auxlà BSD đúng
Jasen

1
Người, 2 điều; (1) tập trung vào khái niệm ví dụ ( pscho thấy cách gọi của bash) không phải là cú pháp rõ ràng; (2) ps -auxhoạt động hoàn hảo trên CentOS 7 và Debian 9 mà không có cảnh báo; rằng cut'n'paste chính xác là đầu ra từ máy của tôi; toàn bộ cuộc thảo luận về việc -có cần thiết hay không có thể áp dụng cho các phiên bản cũ hơn của các gói linux, không phải các phiên bản hiện tại.
Stephen Harris

9

Trên .sh

Điều gì là xấu .sh ở cuối tên tập tin.

Hãy tưởng tượng rằng bạn viết lại một trong các đoạn script trong python.

Bây giờ bạn phải thay đổi dòng đầu tiên để #!/usr/bin/python3điều này không quá tệ, vì bạn cũng phải thay đổi tất cả các dòng mã khác trong tệp. Tuy nhiên, bạn cũng phải thay đổi tên tập tin từ prog.shthành prog.py. Và sau đó bạn phải tìm ở mọi nơi trong các tập lệnh khác và tất cả các tập lệnh người dùng của bạn (những tập lệnh mà bạn thậm chí không biết đã tồn tại) và thay đổi chúng để sử dụng tập lệnh mới.sed -e 's/.sh/.py/g'có thể giúp đỡ cho những người bạn biết về. Nhưng bây giờ công cụ kiểm soát sửa đổi của bạn đang hiển thị một sự thay đổi trong rất nhiều tệp không liên quan.

Hoặc làm theo cách Unix và đặt tên chương trình progkhông prog.sh.

Trên #!

Nếu bạn có chính xác #!và đặt quyền / chế độ để bao gồm thực thi. Sau đó, bạn không cần phải biết thông dịch viên. Máy tính sẽ làm điều đó cho bạn.

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

Đối với các hệ thống không phải là gnu đọc hướng dẫn cho chmod(và tìm hiểu bát phân), có thể đọc nó bằng mọi cách.


1
Hmmm, tiện ích mở rộng không nhất thiết là xấu, ít nhất là chúng giúp liên lạc với những người dùng khác loại tệp.
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy Nhưng bạn không cần biết ngôn ngữ, bạn chỉ cần chạy nó. Ngay cả Microsoft cũng đang cố gắng tránh xa các tiện ích mở rộng (thật không may là chúng tôi đang che giấu chúng). Tuy nhiên tôi đồng ý rằng chúng có thể là một ý tưởng tốt: .imagecho hình ảnh. .audiocho âm thanh, vv Vì vậy, cho bạn biết nó là gì nhưng không phải về việc thực hiện / mã hóa.
ctrl-alt-delor

1
@ ctrl-alt-delor Nếu tệp được gọi launch-missileshoặc delete-project-and-make-manager-pissedtôi không muốn "chỉ chạy nó" khi tôi chỉ tò mò về ngôn ngữ :)
Sergiy Kolodyazhnyy

2
nếu bạn tò mò về ngôn ngữ, hãy sử dụng filelệnh. nó nhận ra hầu hết các loại tập tin.
Jasen

2
Tôi đồng ý rằng các tiện ích mở rộng không tốt cho các tập lệnh thực thi, vì chính xác các lý do được đưa ra. Tôi sử dụng tiện ích mở rộng .sh cho tập lệnh cần được lấy bởi một tập lệnh shell khác (tức là tập lệnh không thể thực thi) - trong kịch bản đó, hậu tố là quan trọng / hợp lệ vì nó cho chúng ta biết làm thế nào chúng ta có thể sử dụng tệp.
Laurence Renshaw

8

[Các dòng hashbang] đảm bảo rằng tập lệnh chỉ chạy với một trình bao nhất định (Bash trong trường hợp đó) để ngăn chặn hành vi không mong muốn trong trường hợp sử dụng trình bao khác.

Có lẽ đáng để chỉ ra rằng điều này là khá sai. Dòng hashbang không có cách nào ngăn bạn cố gắng cung cấp tệp cho trình bao / trình thông dịch không chính xác:

$ 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

(hoặc tương tự với awk -f array.shvv)


Nhưng trong mọi trường hợp, một cách tiếp cận khác về tính mô đun sẽ là xác định các hàm shell trong các tệp, một hàm cho mỗi tệp, nếu bạn muốn, và sau đó sourcelà các tệp từ chương trình chính. Theo cách đó, bạn sẽ không cần hashbang: chúng sẽ được coi như bất kỳ bình luận nào khác và mọi thứ sẽ chạy bằng cùng một vỏ. Nhược điểm là bạn cần sourcecác tệp có chức năng (ví dụ for x in functions/*.src ; do source "$x" ; done) và tất cả chúng đều chạy bằng cùng một vỏ, vì vậy bạn không thể thay thế trực tiếp một trong số chúng bằng cách thực hiện Perl.


+1 ví dụ với mảng bash. Người dùng không quen thuộc với nhiều shell hoặc một số điều POSIX định nghĩa, có thể cho rằng một số tính năng của một shell này tồn tại trong một shell khác. Ngoài ra đối với các chức năng, điều này có thể có liên quan: unix.stackexchange.com/q/313256/85039
Sergiy Kolodyazhnyy

4

Có cách nào để tránh sự dư thừa bị cáo buộc này là 20 hoặc 50 hoặc số lượng khai báo tập lệnh lớn hơn nhiều bằng cách sử dụng một số khai báo tập lệnh "toàn cầu", trong một số tệp chính không?

Có - nó được gọi là tính di động hoặc tuân thủ POSIX. Cố gắng luôn luôn viết các tập lệnh theo cách di động, trung tính shell, chỉ lấy chúng từ một tập lệnh sử dụng #!/bin/shhoặc khi bạn sử dụng /bin/shtương tác (hoặc ít nhất là một vỏ tương thích POSIX như ksh).

Tuy nhiên, tập lệnh di động không cứu bạn khỏi:

  • cần sử dụng các tính năng của một trong các vỏ ( câu trả lời của ilkkachu là một ví dụ)
  • cần sử dụng các ngôn ngữ script cao cấp hơn shell (Python và Perl)
  • Lỗi PEBKAC: tập lệnh của bạn rơi vào tay người dùng J.Doe, người chạy tập lệnh của bạn không như bạn dự định, ví dụ như họ chạy /bin/shtập lệnh với csh.

Nếu tôi có thể bày tỏ ý kiến ​​của mình, "tránh dư thừa" bằng cách loại bỏ #!là mục tiêu sai. Mục tiêu phải là hiệu suất nhất quánthực sự là một kịch bản hoạt động, và xem xét các trường hợp góc, tuân theo các quy tắc chung nhất định như không phân tích cú pháp-ls hoặc luôn luôn trích dẫn-biến-trừ khi cần tách từ .

Chúng giúp người dùng nhớ lại shell nào cần thiết để thực thi tệp (giả sử, sau một số năm không sử dụng tệp).

Chúng thực sự không dành cho người dùng - chúng dành cho hệ điều hành để chạy trình thông dịch phù hợp. "Đúng" trong trường hợp này, có nghĩa là HĐH tìm thấy những gì trong shebang; nếu mã bên dưới nó sai vỏ - đó là lỗi của tác giả. Đối với bộ nhớ người dùng, tôi không nghĩ "người dùng" các chương trình như Microsoft Word hay Google Chrome cần bao giờ để biết chương trình ngôn ngữ nào được viết; tác giả có thể cần.

Sau đó, một lần nữa, các tập lệnh được viết một cách hợp lý có thể loại bỏ thậm chí cần phải nhớ lớp vỏ nào bạn đã sử dụng ban đầu, bởi vì các tập lệnh di động sẽ hoạt động trong bất kỳ trình bao tuân thủ POSIX nào.

Họ đảm bảo rằng tập lệnh chỉ chạy với một trình bao nhất định (Bash trong trường hợp đó) để ngăn chặn hành vi không mong muốn trong trường hợp một trình bao khác được sử dụng.

Họ không. Điều thực sự ngăn chặn hành vi bất ngờ là viết các kịch bản di động.


1

Lý do tại sao bạn cần đặt #!/bin/bashở đầu mỗi tập lệnh là vì bạn đang chạy nó dưới dạng một quy trình riêng biệt.

Khi bạn chạy tập lệnh như một quy trình mới, HĐH sẽ thấy rằng đó là tệp văn bản (trái ngược với tệp thực thi nhị phân) và cần biết trình thông dịch nào sẽ thực thi. Nếu bạn không nói với nó, HĐH chỉ có thể đoán, sử dụng cài đặt mặc định của nó (mà quản trị viên có thể thay đổi). Vì vậy, #!dòng (cộng với việc thêm quyền thực thi, tất nhiên) biến một tệp văn bản đơn giản thành tệp thực thi có thể chạy trực tiếp, với sự tự tin rằng HĐH biết phải làm gì với nó.

Nếu bạn muốn xóa #!dòng, thì các tùy chọn của bạn là:

  1. Thực thi tập lệnh trực tiếp (như bạn đang làm bây giờ) và hy vọng rằng trình thông dịch mặc định khớp với tập lệnh - có thể bạn an toàn trên hầu hết các hệ thống Linux, nhưng không thể di chuyển sang các hệ thống khác (ví dụ Solaris) và có thể thay đổi thành bất cứ điều gì (tôi thậm chí có thể thiết lập nó để chạy vimtrên tập tin!).

  2. Thực thi tập lệnh bằng cách sử dụng bash myscript.sh. Điều này hoạt động tốt, nhưng bạn phải xác định đường dẫn đến kịch bản và nó lộn xộn.

  3. Nguồn tập lệnh sử dụng . myscript.sh, chạy tập lệnh trong quy trình thông dịch hiện tại (nghĩa là không phải là quy trình phụ). Nhưng điều này gần như chắc chắn sẽ yêu cầu sửa đổi các tập lệnh của bạn và làm cho chúng ít mô đun / tái sử dụng hơn.

Trong trường hợp 2 và 3, tệp không cần (và không nên) thực thi quyền và cung cấp cho nó một hậu tố .sh(hoặc .bash) là một ý tưởng tốt.

Nhưng không có tùy chọn nào trong số đó có vẻ tốt cho dự án của bạn, như bạn đã nói rằng bạn muốn giữ các tập lệnh của mình ở dạng mô-đun (=> độc lập và khép kín), vì vậy bạn nên giữ #!/bin/bashdòng của mình ở đầu các tập lệnh.

Trong câu hỏi của bạn, bạn cũng nói rằng bạn đang theo triết lý Unix. Nếu đó là sự thật, thì bạn nên tìm hiểu xem #!dòng này thực sự dùng để làm gì và tiếp tục sử dụng nó trong mỗi tập lệnh thực thi!


Cảm ơn câu trả lời tốt. Tôi yêu tất cả mọi thứ trong câu trả lời và nghĩ đến việc nâng cao cho đến khi này : then you should learn what the #! line is really for, and keep using it in every executable script!. Người ta có thể theo U.Philatics lên o một số điểm kiến ​​thức nhất định. Sau tất cả những câu trả lời tôi đã hiểu tại sao nó có thể được đưa vào thuật ngữ đó. Tôi đề nghị chỉnh sửa câu trả lời, thay thế nó bằng "Xem shebang như một phần nội bộ của Triết lý Unix mà bạn tôn trọng và chấp nhận".
dùng9303970

1
Xin lỗi nếu tôi là trực tiếp, hoặc nếu nhận xét đó có vẻ thô lỗ. Nhận xét của bạn đề xuất rằng bạn thích làm việc trong IDE - với 'dự án' cho phép bạn xác định quy tắc toàn cầu cho các tập lệnh trong dự án đó. Đó là một cách hợp lệ để phát triển, nhưng nó không phù hợp với việc coi mỗi tập lệnh là một chương trình độc lập, độc lập (triết lý Unix 'mà bạn đã đề cập).
Laurence Renshaw
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.