#! / bin / sh so với #! / bin / bash để có tính di động tối đa


36

Tôi thường làm việc với các máy chủ Ubuntu LTS mà từ những gì tôi hiểu liên kết tượng trưng /bin/shđến /bin/dash. Rất nhiều distro khác mặc dù symlink /bin/shđến /bin/bash.

Từ đó tôi hiểu rằng nếu một tập lệnh sử dụng #!/bin/shtrên đầu thì nó có thể không chạy cùng một cách trên tất cả các máy chủ?

Có một thực tiễn được đề xuất về việc sử dụng shell nào cho các script khi người ta muốn tính di động tối đa của các script đó giữa các máy chủ không?


Có sự khác biệt nhỏ giữa các loại vỏ khác nhau. Nếu tính di động là quan trọng nhất đối với bạn thì hãy sử dụng #!/bin/shvà không sử dụng bất cứ thứ gì khác ngoài những gì vỏ ban đầu được cung cấp.
Thorbjørn Ravn Andersen

Câu trả lời:


65

Có khoảng bốn mức độ di động cho các tập lệnh shell (theo như dòng shebang có liên quan):

  1. Di động nhất: sử dụng #!/bin/shshebang và chỉ sử dụng cú pháp shell cơ bản được chỉ định trong tiêu chuẩn POSIX . Điều này sẽ hoạt động trên hầu hết mọi hệ thống POSIX / unix / linux. (Chà, ngoại trừ Solaris 10 và trước đó có vỏ Bourne di sản thực sự, trước POSIX nên không tuân thủ, như /bin/sh.)

  2. Thứ hai di động nhất: sử dụng một dòng #!/bin/bash(hoặc #!/usr/bin/env bash) shebang và bám vào các tính năng bash v3. Điều này sẽ hoạt động trên bất kỳ hệ thống nào có bash (ở vị trí dự kiến).

  3. Thứ ba di động nhất: sử dụng một dòng #!/bin/bash(hoặc #!/usr/bin/env bash) shebang và sử dụng các tính năng bash v4. Điều này sẽ thất bại trên bất kỳ hệ thống nào có bash v3 (ví dụ: macOS, phải sử dụng nó vì lý do cấp phép).

  4. Ít di động nhất: sử dụng #!/bin/shshebang và sử dụng các phần mở rộng bash cho cú pháp shell POSIX. Điều này sẽ thất bại trên bất kỳ hệ thống nào có nội dung khác ngoài bash for / bin / sh (chẳng hạn như các phiên bản Ubuntu gần đây). Đừng bao giờ làm điều này; nó không chỉ là một vấn đề tương thích, nó hoàn toàn sai. Thật không may, đó là một lỗi rất nhiều người mắc phải.

Đề nghị của tôi: sử dụng cách bảo thủ nhất trong ba phần đầu cung cấp tất cả các tính năng shell mà bạn cần cho tập lệnh. Để có tính di động tối đa, hãy sử dụng tùy chọn # 1, nhưng theo kinh nghiệm của tôi, một số tính năng bash (như mảng) đủ hữu ích mà tôi sẽ sử dụng với # 2.

Điều tồi tệ nhất bạn có thể làm là # 4, sử dụng sai shebang. Nếu bạn không chắc chắn các tính năng nào là POSIX cơ bản và các tiện ích mở rộng bash, hãy sử dụng bash shebang (ví dụ tùy chọn # 2) hoặc kiểm tra kỹ kịch bản với trình bao rất cơ bản (như dấu gạch ngang trên máy chủ Ubuntu LTS của bạn). Các wiki Ubuntu có một danh sách tốt các bashism cần chú ý .

Có một số thông tin rất hay về lịch sử và sự khác biệt giữa các shell trong câu hỏi Unix & Linux "Có nghĩa là gì để tương thích với sh?" và câu hỏi Stackoverflow "Sự khác biệt giữa sh và bash" .

Ngoài ra, hãy lưu ý rằng lớp vỏ không phải là thứ duy nhất khác nhau giữa các hệ thống khác nhau; nếu bạn đã quen với linux, bạn đã quen với các lệnh GNU, có rất nhiều phần mở rộng không chuẩn mà bạn không thể tìm thấy trên các hệ thống unix khác (ví dụ: bsd, macOS). Thật không may, không có quy tắc đơn giản nào ở đây, bạn chỉ cần biết phạm vi biến thể cho các lệnh bạn đang sử dụng.

Một trong những lệnh khó nhất về tính di động là một trong những lệnh cơ bản nhất : echo. Bất cứ khi nào bạn sử dụng nó với bất kỳ tùy chọn nào (ví dụ echo -nhoặc echo -e) hoặc với bất kỳ lối thoát nào (dấu gạch chéo ngược) trong chuỗi để in, các phiên bản khác nhau sẽ làm những việc khác nhau. Bất cứ khi nào bạn muốn in một chuỗi mà không có nguồn cấp dữ liệu sau chuỗi hoặc thoát trong chuỗi, hãy sử dụng printfthay thế (và tìm hiểu cách thức hoạt động của chuỗi - nó phức tạp hơn nhiều echo). Các pslệnh là cũng là một mớ hỗn độn .

Một điều chung khác cần theo dõi là cú pháp tùy chọn lệnh mở rộng / GNUish gần đây: định dạng lệnh cũ (tiêu chuẩn) là lệnh được theo sau bởi các tùy chọn (với một dấu gạch ngang và mỗi tùy chọn là một chữ cái), theo sau là đối số lệnh. Các biến thể gần đây (và thường không di động) bao gồm các tùy chọn dài (thường được giới thiệu --), cho phép các tùy chọn xuất hiện sau các đối số và sử dụng --để tách các tùy chọn khỏi các đối số.


12
Lựa chọn thứ tư đơn giản là một ý tưởng sai lầm. Xin đừng sử dụng nó.
pabouk

3
@pabouk Tôi hoàn toàn đồng ý, vì vậy tôi đã chỉnh sửa câu trả lời của mình để làm cho điều này rõ ràng hơn.
Gordon Davisson

1
Tuyên bố đầu tiên của bạn là hơi sai lệch. Tiêu chuẩn POSIX không chỉ định bất cứ điều gì về shebang bên ngoài khi sử dụng nó dẫn đến hành vi không xác định. Ngoài ra, POSIX không chỉ định vị trí của vỏ posix, chỉ tên của nó ( sh), do đó /bin/shkhông được đảm bảo là đường dẫn đúng. Di động nhất sau đó là không chỉ định bất kỳ shebang nào, hoặc để điều chỉnh shebang với HĐH được sử dụng.
jlliagre

2
Gần đây tôi đã rơi vào vị trí thứ 4 với một kịch bản của tôi và tôi không thể hiểu tại sao nó không hoạt động; Rốt cuộc, các lệnh tương tự đã hoạt động ổn định và thực hiện chính xác những gì tôi muốn chúng làm, khi tôi thử chúng trực tiếp trong vỏ. Ngay sau khi tôi thay đổi #!/bin/shđể #!/bin/bashtuy nhiên, kịch bản làm việc một cách hoàn hảo. (Để bảo vệ tôi, kịch bản đã phát triển theo thời gian từ một mà thực sự chỉ cần sh-ISMS, cho rằng một trong dựa vào bash giống như hành vi.)
một CVN

2
@ MichaelKjorling Vỏ Bourne (thật) hầu như không bao giờ được đóng gói với các bản phân phối Linux và dù sao cũng không tuân thủ POSIX. Shell tiêu chuẩn POSIX được tạo ra từ kshhành vi, không phải Bourne. Điều mà hầu hết các bản phân phối Linux sau FHS làm là có /bin/shmột liên kết tượng trưng đến hệ vỏ mà họ chọn để cung cấp khả năng tương thích POSIX, thường bashhoặc dash.
jlliagre

5

Trong ./configurekịch bản chuẩn bị ngôn ngữ TXR để xây dựng, tôi đã viết phần mở đầu sau đây để có tính di động tốt hơn. Kịch bản sẽ tự khởi động ngay cả khi #!/bin/shlà Bourne Shell cũ không phù hợp với POSIX. (Tôi xây dựng mọi bản phát hành trên Solaris 10 VM).

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell

Ý tưởng ở đây là chúng tôi tìm thấy một trình bao tốt hơn so với trình bao chúng tôi đang chạy và thực hiện lại tập lệnh bằng cách sử dụng trình bao đó. Biến txr_shellmôi trường được đặt, để tập lệnh được thực thi lại biết đó là trường hợp đệ quy được thực hiện lại.

(Trong kịch bản của tôi, các txr_shellbiến được cũng sau đó được sử dụng, cho chính xác hai mục đích: thứ nhất nó được in như một phần của một thông báo thông tin ở đầu ra của kịch bản Thứ hai, nó được cài đặt như là. SHELLBiến trong Makefile, do đó makesẽ sử dụng này shell quá để thực hiện công thức nấu ăn.)

Trên một hệ thống có /bin/shdấu gạch ngang, bạn có thể thấy rằng logic trên sẽ tìm /bin/bashvà thực hiện lại tập lệnh với điều đó.

Trên hộp Solaris 10, ý /usr/xpg4/bin/shchí sẽ được kích hoạt nếu không tìm thấy Bash.

Phần mở đầu được viết theo phương ngữ shell bảo thủ, sử dụng testcho các bài kiểm tra tồn tại tệp và ${@+"$@"}mẹo để mở rộng các đối số phục vụ cho một số shell cũ bị hỏng (đơn giản là "$@"nếu chúng ta ở trong vỏ tuân thủ POSIX).


Người ta sẽ không cần đến tin xtặc nếu sử dụng trích dẫn thích hợp, vì các tình huống bắt buộc thành ngữ đó bao quanh các yêu cầu kiểm tra không dùng nữa với -ahoặc -okết hợp nhiều bài kiểm tra.
Charles Duffy

@CharlesDuffy Thật vậy; cái test x$whatevermà tôi đang tồn tại ở đó trông giống như một củ hành trong vecni. Nếu chúng ta không thể tin vào cái vỏ cũ bị hỏng để trích dẫn, thì ${@+"$@"}nỗ lực cuối cùng là vô nghĩa.
Kaz

2

Tất cả các biến thể của ngôn ngữ shell Bourne là khách quan khủng khiếp so với các ngôn ngữ kịch bản hiện đại như Perl, Python, Ruby, node.js và thậm chí (có thể nói là) Tcl. Nếu bạn phải làm bất cứ điều gì thậm chí hơi phức tạp một chút, bạn sẽ hạnh phúc hơn về lâu dài nếu bạn sử dụng một trong những điều trên thay vì một kịch bản shell.

Ưu điểm duy nhất và duy nhất mà ngôn ngữ shell vẫn có trên các ngôn ngữ mới hơn đó là thứ gì đó tự gọi /bin/shlà được đảm bảo tồn tại trên bất kỳ thứ gì có ý nghĩa là Unix. Tuy nhiên, thứ gì đó thậm chí có thể không tuân thủ POSIX; nhiều Unix độc quyền kế thừa đã đóng băng ngôn ngữ được triển khai bởi /bin/shvà các tiện ích trong PATH mặc định trước những thay đổi mà Unix95 yêu cầu (vâng, Unix95, hai mươi năm trước và đang tính). Có thể có một bộ Unix95 hoặc thậm chí POSIX.1-2001 nếu bạn may mắn, các công cụ trong một thư mục không có trên PATH mặc định (ví dụ /usr/xpg4/bin) nhưng chúng không được bảo đảm tồn tại.

Tuy nhiên, những điều cơ bản của Perl có nhiều khả năng có mặt trên bản cài đặt Unix được chọn tùy ý so với Bash. (Theo "những điều cơ bản của Perl", ý tôi là /usr/bin/perltồn tại và là một số , có thể khá cũ, phiên bản của Perl 5, và nếu bạn may mắn, bộ mô-đun đi kèm với phiên bản phiên dịch đó cũng có sẵn.)

Vì thế:

Nếu bạn đang viết một cái gì đó phải hoạt động ở mọi nơi có nghĩa là Unix (chẳng hạn như tập lệnh "configure"), bạn cần sử dụng #! /bin/shvà bạn không cần sử dụng bất kỳ tiện ích mở rộng nào. Ngày nay tôi sẽ viết vỏ tương thích POSIX.1-2001 trong trường hợp này, nhưng tôi sẽ sẵn sàng vá lỗi POSIXism nếu có ai đó yêu cầu hỗ trợ cho sắt gỉ.

Nhưng nếu bạn không viết một cái gì đó phải hoạt động ở mọi nơi, thì ngay lúc đó bạn muốn sử dụng bất kỳ Bashism nào, bạn nên dừng lại và viết lại toàn bộ bằng một ngôn ngữ kịch bản tốt hơn. Tương lai của bạn sẽ cảm ơn bạn.

(Vì vậy, khi được nó thích hợp để sử dụng phần mở rộng Bash Để đặt hàng đầu tiên: không bao giờ Để trật tự thứ hai: chỉ để mở rộng môi trường tương tác Bash - ví dụ để cung cấp bằng tab hoàn thành và lạ mắt nhắc nhở thông minh?..)


Hôm nọ tôi phải viết một kịch bản đã làm một cái gì đó như thế find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. Sẽ không thể làm cho nó hoạt động chính xác mà không có read -d ""phần mở rộng bashism ( ) và GNU ( find -print0, tar --null). Thực hiện lại dòng đó trong Perl sẽ dài hơn và vụng về hơn. Đây là loại tình huống khi sử dụng bashism và các phần mở rộng không phải POSIX khác là Điều phải làm.
michau

@michau Nó có thể không đủ điều kiện là một lớp lót nữa, tùy thuộc vào những gì diễn ra trong các hình elip đó, nhưng tôi nghĩ rằng bạn không dùng "viết lại toàn bộ nội dung bằng ngôn ngữ kịch bản tốt hơn" theo nghĩa đen của nó. Cách của tôi trông giống như perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdThông báo rằng không chỉ bạn không cần bất kỳ bashism nào, bạn không cần bất kỳ phần mở rộng tar GNU nào. (Nếu độ dài dòng lệnh là một mối quan tâm hoặc bạn muốn quét và tar chạy song song, thì bạn cần tar --null -T.)
zwol

Vấn đề là, đã findtrả lại hơn 50.000 tên tệp trong kịch bản của tôi, vì vậy tập lệnh của bạn có khả năng đạt giới hạn độ dài đối số. Việc triển khai chính xác không sử dụng các tiện ích mở rộng không phải POSIX sẽ phải tăng dần sản lượng tar hoặc có thể sử dụng Archive :: Tar :: Stream trong mã Perl. Tất nhiên, nó có thể được thực hiện, nhưng trong trường hợp này, tập lệnh Bash viết nhanh hơn nhiều và có ít bản tóm tắt hơn, và do đó ít có chỗ cho lỗi hơn.
michau

BTW, tôi có thể sử dụng Perl thay vì Bash một cách nhanh chóng và súc tích : find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Nhưng câu hỏi là: 1) Tôi đang sử dụng find -print0tar --nulldù sao, vì thế quan điểm tránh bashism là những gì read -d "", mà là chính xác cùng một loại điều (một phần mở rộng GNU để xử lý rỗng phân cách, không giống như Perl, có thể trở thành một POSIX điều một ngày nào đó )? 2) Perl có thực sự lan rộng hơn Bash không? Gần đây tôi đã sử dụng hình ảnh Docker và rất nhiều trong số chúng có Bash nhưng không có Perl. Các tập lệnh mới có nhiều khả năng được chạy ở đó, hơn là trên các Unice cổ đại.
michau
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.