Phổ biến Node.js shebang?


42

Node.js ngày nay rất phổ biến và tôi đã viết một số tập lệnh trên đó. Thật không may, khả năng tương thích là một vấn đề. Chính thức, trình thông dịch Node.js được gọi là node, nhưng Debian và Ubuntu gửi một tệp thực thi được gọi nodejsthay thế.

Tôi muốn các tập lệnh di động mà Node.js có thể làm việc với càng nhiều tình huống càng tốt. Giả sử tên tệp là foo.js, tôi thực sự muốn tập lệnh chạy theo hai cách:

  1. ./foo.jschạy tập lệnh nếu một trong hai nodehoặc nodejstrong $PATH.
  2. node foo.jscũng chạy tập lệnh (giả sử trình thông dịch được gọi node)

Lưu ý: Các câu trả lời của xavierm02 và bản thân tôi là hai biến thể của tập lệnh polyglot. Tôi vẫn quan tâm đến một giải pháp shebang tinh khiết, nếu như vậy tồn tại.


Tôi nghĩ rằng không có giải pháp thực sự cho vấn đề này, vì người ta được phép đặt tên cho hệ thống xây dựng một cách tự nhiên bằng các hệ thống xây dựng. Không có gì ngăn bạn đặt tên cho trình thông dịch python alphómauri, bạn chỉ cần làm theo các quy ước và đặt tên cho nó là python. Tôi khuyên bạn nên sử dụng tên tiêu chuẩn nodecho tập lệnh của mình hoặc có một loại tập lệnh tạo sửa đổi shebang.

@ G.Kayaalp Chính trị và quy ước sang một bên, có rất nhiều người dùng Debian / Ubuntu / Fedora và tôi muốn tạo ra các kịch bản phù hợp với họ. Tôi không muốn thiết lập một hệ thống xây dựng cho việc này (bất kỳ ai xây dựng các tập lệnh shell trước khi chạy chúng?), Tôi cũng không muốn hỗ trợ alphacentaurivà như vậy. Nếu có một tệp thực thi được gọi nodejs, bạn có thể chắc chắn 99% đó là Node.js. Tại sao không hỗ trợ cả hai nodejsnode?
nhảy

Cài đặt gói nodejs-legacy. Lý do điều này là cần thiết là tên quá kiêu ngạo chung chung, người khác có tên đầu tiên. Gói khác đó, tuy nhiên, sẵn sàng chia sẻ tên.
dùng3710044

Câu trả lời:


54

Điều tốt nhất tôi nghĩ ra là "shebang hai dòng" này thực sự là một tập lệnh polyglot (Bourne shell / Node.js):

#!/bin/sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

console.log('Hello world!');

Dòng đầu tiên, rõ ràng là một shebang vỏ Bourne. Node.js bỏ qua bất kỳ shebang nào mà nó tìm thấy, vì vậy đây là tệp javascript hợp lệ theo như Node.js có liên quan.

Dòng thứ hai gọi shell no-op :với đối số //và sau đó thực thi nodejshoặc nodevới tên của tệp này làm tham số. command -vđược sử dụng thay vì whichcho tính di động. Cú pháp thay thế lệnh $(...)không đúng với Bourne, vì vậy hãy chọn backticks nếu bạn chạy nó trong những năm 1980.

Node.js chỉ đánh giá chuỗi ':', giống như no-op và phần còn lại của dòng được phân tích cú pháp dưới dạng một nhận xét.

Phần còn lại của tệp chỉ là javascript cũ. Các subshell thoát sau khi execdòng thứ hai được hoàn thành, vì vậy phần còn lại của tệp không bao giờ được đọc bởi shell.

Cảm ơn xavierm02 đã truyền cảm hứng và tất cả các bình luận viên để biết thêm thông tin!


1
Giải pháp tuyệt vời. Một cách khác để ':'tiếp cận là sử dụng // 2>/dev/null(đó là những gì nvmhiện có): bash, đó là một lỗi ( bash: //: Is a directory), mà chuyển hướng 2>/dev/nulllặng lẽ bỏ qua; với JavaScript, toàn bộ dòng trở thành một nhận xét. Ngoài ra - và tôi không hy vọng đây sẽ là một vấn đề IRL - commandcó một sự giải quyết trong đó nó cũng báo cáo các hàm và bí danh shell -v- mặc dù nó sẽ không thực sự gọi chúng. Do đó, nếu bạn tình cờ có các hàm shell xuất hoặc thậm chí các bí danh (giả sử shopt -s expand_aliases) được đặt tên nodehoặc nodejs, mọi thứ có thể bị phá vỡ.
mkuity0

1
Chà, POSIX sh có mặt ở khắp mọi nơi trong những ngày này (ngoại trừ Solaris / bin / sh - nhưng Solaris đi kèm với AT & T ksh ra khỏi hộp, tương thích với POSIX), và Markus Kuhn khuyên ( nên biện minh). IMHO bạn không bao giờ cần nó. Nhưng vâng, nó đủ cho mksh, bash, dashvà vỏ khác trong các hệ thống Unix và GNU hiện đại.
mirabilos

1
Giải pháp tuyệt vời; mặc dù thậm chí nhiều khả năng di động hơn sẽ được sử dụng #!/usr/bin/env shthay vì #!/bin/sh(per en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089

2
Tôi sẽ cẩn thận về quảng cáo #!/usr/bin/env shlà "di động hơn". Bài viết trên Wikipedia cũng nói rằng "Điều này chủ yếu hoạt động vì đường dẫn / usr / bin / env thường được sử dụng cho tiện ích env ..." Đó không phải là một sự chứng thực tuyệt vời và tôi đoán là bạn sẽ chạy vào các hệ thống mà không /usr/bin/envthường xuyên hơn bạn chạy vào các hệ thống mà không /bin/shcó sự khác biệt về tần số.
sheldonh

1
@dancek Xin chào từ năm 2018, sẽ không phải là //bin/sh -c :; exec ...phiên bản sạch hơn của dòng thứ hai?
har-wradim

10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falsecũng giống như /bin/falsengoại trừ dấu gạch chéo thứ hai biến nó thành một nhận xét cho nút và đó là lý do tại sao nó ở đây. Sau đó, phía bên phải của đầu tiên ||được đánh giá. 'which node || which nodejs'với backquote thay vì dấu ngoặc kép khởi chạy nút và <<nguồn cấp dữ liệu cho nó bất cứ điều gì ở bên phải. Tôi đã có thể sử dụng một dấu phân cách bắt đầu //giống như dancek đã làm, nó sẽ hoạt động nhưng tôi thấy nó sạch hơn khi chỉ có hai dòng ở đầu nên tôi thường tail -n +2 $0đọc tệp ngoại trừ hai dòng đầu tiên.

Và nếu bạn chạy nó trong nút, dòng đầu tiên được nhận dạng là một shebang và bị bỏ qua và dòng thứ hai là một nhận xét một dòng.

(Rõ ràng, sed có thể được sử dụng để thay thế đuôi Nội dung tệp in mà không có dòng đầu tiên và dòng cuối cùng )


Trả lời trước khi chỉnh sửa:

#!/bin/sh
`which node || which nodejs` <<__HERE__
console.log('ok');
__HERE__

Bạn không thể làm những gì bạn muốn vì vậy những gì bạn làm thay vào đó là chạy một kịch bản shell, do đó #!/bin/sh. Kịch bản shell đó sẽ lấy đường dẫn của tệp cần thiết để thực thi nút, nghĩa là which node || which nodejs. Các backquote ở đây để nó được thực thi, vì vậy 'which node || which nodejs'(với các backquote thay vì dấu ngoặc kép) chỉ cần gọi nút. Sau đó, bạn chỉ cần cung cấp kịch bản của bạn cho nó với <<. Các __HERE__dấu phân cách của tập lệnh của bạn. Và đây console.log('ok');là một ví dụ về tập lệnh mà bạn nên thay thế bằng tập lệnh của mình.


một lời giải thích sẽ rất hay
0xC0000022L

Trích dẫn tốt hơn dấu phân cách tài liệu ở đây để tránh mở rộng tham số, thay thế lệnh và mở rộng số học được thực hiện trên mã JavaScript trước khi thực hiện : <<'__HERE__'.
manatwork

Điều này đang trở nên thú vị! Mặc dù //bin/falsekhông hoạt động trong môi trường MSYS của tôi và tôi cần các trích dẫn xung quanh các backticks khi tôi nodecư trú tại C:\Program Files\.... Phải, tôi làm việc trong một môi trường khủng khiếp ...
dancek

1
//bin/falsecũng không hoạt động trên Mac OS X. Tôi xin lỗi, nhưng điều này dường như không còn di động nữa.
nhảy

2
Các whichlệnh cũng không phải là cầm tay.
jordanm

9

Đây chỉ là một vấn đề trên các hệ thống dựa trên Debian, nơi chính sách đã vượt qua ý nghĩa.

Tôi không biết khi Fedora cung cấp một nhị phân gọi là nodejs, nhưng tôi chưa bao giờ thấy nó. Gói được gọi là nodejs và nó cài đặt một nút nhị phân gọi là nút.

Chỉ cần sử dụng một liên kết tượng trưng để áp dụng ý thức chung cho các hệ thống dựa trên Debian của bạn và sau đó bạn có thể sử dụng một shebang lành mạnh. Những người khác dù sao cũng sẽ sử dụng shebang sane, vì vậy bạn sẽ cần liên kết tượng trưng đó.

#!/usr/bin/env node

console.log("Spread the love.");

Tôi xin lỗi, nhưng điều này không trả lời câu hỏi. Tôi hiểu quan điểm chính trị, nhưng đó không phải là vấn đề ở đây.
nhảy

Đối với hồ sơ, một số hệ thống Fedora có thể nodejsthực thi được , nhưng đó không phải là lỗi của Fedora. Xin lỗi vì đã trình bày sai sự thật.
nhảy

8
Không cần phải xin lỗi. Bạn khá đúng. Câu trả lời của tôi không trả lời câu hỏi của bạn. Nó nói lên câu hỏi của bạn, cho thấy rằng câu hỏi giới hạn phạm vi đối với các giải pháp ít hữu ích hơn là có sẵn. Tôi đang cung cấp lời khuyên thiết thực được thông báo bởi lịch sử. Node không phải là thông dịch viên đầu tiên tìm thấy chính nó ở đây. Bạn nên thấy sự hỗn loạn dẫn đến việc Larry Wall tuyên bố rằng perl shebang PHẢI #!/usr/bin/perl. Điều đó nói rằng, bạn không bắt buộc phải thích hoặc áp dụng các đề xuất của tôi. Sự thanh bình.
sheldonh

3

Nếu bạn không phiền khi tạo một .shtệp nhỏ , tôi sẽ có một giải pháp nhỏ cho bạn. Bạn có thể tạo một tập lệnh shell nhỏ để xác định nút nào có thể thực thi được và sử dụng tập lệnh này trong shebang của bạn:

shebang.sh :

#!/bin/sh
`which node || which nodejs` $@

script.js :

#!./shebang.sh
console.log('hello');

Đánh dấu cả thực thi và chạy ./script.js.

Bằng cách này, bạn tránh được kịch bản polyglot. Tôi không nghĩ sử dụng nhiều dòng shebang là có thể, mặc dù có vẻ như đó là một ý tưởng tốt.

Mặc dù điều này giải quyết vấn đề theo cách bạn muốn, nhưng dường như không ai quan tâm đến điều này. Ví dụ, uglifyjscoffeescript sử dụng #!/usr/bin/env node, NPM sử dụng một kịch bản shell như một điểm nhập cảnh, trong đó một lần nữa kêu gọi thực thi một cách rõ ràng với tên node. Tôi là người dùng Ubuntu và tôi không biết điều này vì tôi luôn biên dịch nút. Tôi đang xem xét để báo cáo đây là một lỗi.


Tôi khá chắc chắn rằng ít nhất bạn phải yêu cầu người dùng sử dụng chmod +xtập lệnh sh ... vì vậy bạn cũng có thể yêu cầu họ đặt một biến cho vị trí để nút thực thi của họ ...
xavierm02

@ xavierm02 Người ta có thể phát hành tập lệnh chmod +x'd. Và tôi đồng tình rằng một biến NODE tốt hơn which node || which nodejs. Nhưng người hỏi muốn cung cấp trải nghiệm vượt trội, mặc dù nhiều dự án nút lớn chỉ sử dụng #!/usr/bin/env node.

1

Để hoàn thiện, đây là một vài cách khác để thực hiện dòng thứ hai:

// 2>/dev/null || echo yes
false //|| echo yes

Nhưng không có bất kỳ lợi thế nào so với câu trả lời được chọn:

':' //; || echo yes

Ngoài ra, nếu bạn biết rằng một nodehoặc nodejs(nhưng không phải cả hai) sẽ được tìm thấy, các công việc sau:

exec `command -v node nodejs` "$0" "$@"

Nhưng đó là một chữ "nếu" lớn, vì vậy tôi nghĩ rằng câu trả lời được chọn vẫn là câu trả lời hay nhất.


Ngoài ra, bạn có thể chạy bất kỳ foobar // 2>/dev/nullkhi nào foobarkhông phải là lệnh. Và nhiều tiện ích được tìm thấy trên bất kỳ hệ thống POSIX nào cũng có thể được chạy với //đối số.
nhảy

1

Tôi hiểu rằng điều này không trả lời câu hỏi, nhưng tôi tin rằng câu hỏi được hỏi với tiền đề sai.

Ubuntu là sai ở đây. Viết một shebang phổ quát cho tập lệnh của riêng bạn sẽ không thay đổi các gói khác mà bạn không có quyền kiểm soát đối với việc sử dụng tiêu chuẩn thực tế #!/usr/bin/env node. Hệ thống của bạn phải cung cấp nodetại PATHnếu nó muốn cho bất kỳ kịch bản nhắm mục tiêu nodejs để chạy trên nó.

Ví dụ, ngay cả gói do Ubuntu cung cấp cũng npmviết lại các shebang trong các gói:

$ cd
$ rm -rf test_npm
$ mkdir test_npm
$ cd test_npm
$ npm install mkdirp 2>&1 | grep -ve home
`-- mkdirp@0.5.1
  `-- minimist@0.0.8

npm WARN test_npm No description
npm WARN test_npm No repository field.
npm WARN test_npm No README data
npm WARN test_npm No license field.
$ node_modules/.bin/mkdirp testdir
/usr/bin/env: 'node': No such file or directory
$ head -n1 node_modules/.bin/mkdirp
#!/usr/bin/env node
$ npm --version
3.5.2
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
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.