Tại sao RC.local không chạy tất cả các lệnh của tôi và tôi có thể làm gì với nó?


35

Tôi có đoạn rc.localscript sau :

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

sh /home/incero/startup_script.sh
/bin/chmod +x /script.sh
sh /script.sh

exit 0

Dòng đầu tiên, startup_script.shthực sự tải script.shtập tin để tham /script.shchiếu trong dòng thứ ba.

Thật không may, có vẻ như nó không làm cho tập lệnh thực thi hoặc chạy tập lệnh. Chạy rc.localtệp thủ công sau khi đã bắt đầu hoạt động hoàn hảo. Chmod có thể không được chạy khi khởi động hay không?

Câu trả lời:


70

Bạn có thể bỏ qua tất cả các cách để sửa chữa nhanh nhưng đó không nhất thiết là lựa chọn tốt nhất. Vì vậy, tôi khuyên bạn nên đọc tất cả điều này đầu tiên.

rc.local không chịu được lỗi.

rc.localkhông cung cấp một cách để phục hồi thông minh từ các lỗi. Nếu bất kỳ lệnh nào thất bại, nó dừng chạy. Dòng đầu tiên #!/bin/sh -e, làm cho nó được thực thi trong một vỏ được gọi với -ecờ. Các -elá cờ là những gì làm cho một kịch bản (trong trường hợp này, rc.local) ngừng chạy lần đầu tiên một lệnh không bên trong nó.

Bạn muốn rc.localcư xử như thế này. Nếu một lệnh thất bại, bạn không muốn nó tiếp tục với bất kỳ lệnh khởi động nào khác có thể dựa vào nó đã thành công.

Vì vậy, nếu bất kỳ lệnh nào thất bại, các lệnh tiếp theo sẽ không chạy. Vấn đề ở đây là nó /script.shkhông chạy (không phải là nó bị lỗi, xem bên dưới), vì vậy rất có thể là một số lệnh trước khi nó thất bại. Nhưng cái nào?

Có phải vậy /bin/chmod +x /script.shkhông?

Không.

chmodchạy tốt bất cứ lúc nào Với điều kiện hệ thống tập tin chứa /binđược gắn kết, bạn có thể chạy /bin/chmod. Và /binđược gắn trước khi rc.localchạy.

Khi chạy như root, /bin/chmodhiếm khi thất bại. Nó sẽ thất bại nếu tệp mà nó hoạt động ở chế độ chỉ đọc và có thể thất bại nếu hệ thống tệp đó không có quyền được hỗ trợ. Không có khả năng ở đây.

Nhân tiện, đó sh -elà lý do duy nhất nó thực sự sẽ là một vấn đề nếu chmodthất bại. Khi bạn chạy một tệp script bằng cách gọi rõ ràng trình thông dịch của nó, sẽ không có vấn đề gì nếu tệp được đánh dấu thực thi. Chỉ khi nó nói /script.shsẽ có vấn đề bit thực thi của tập tin. Vì nó nói sh /script.sh, nó không (trừ khi tất nhiên /script.sh tự gọi nó trong khi nó chạy, điều này có thể thất bại do nó không thể thực thi được, nhưng không chắc là nó tự gọi nó).

Vậy điều gì đã thất bại?

sh /home/incero/startup_script.shthất bại. Gần như chắc chắn.

Chúng tôi biết nó đã chạy, bởi vì nó được tải xuống /script.sh.

(Nếu không, điều quan trọng là phải đảm bảo rằng nó đã chạy, trong trường hợp bằng cách nào đó /binkhông có trong PATH - rc.localkhông nhất thiết phải giống PATHnhư bạn có khi bạn đăng nhập. Nếu /binkhông có rc.localđường dẫn, thì đây là sẽ yêu cầu shđược chạy như /bin/sh. Vì nó đã chạy, /bintrong PATHđó, có nghĩa là bạn có thể chạy các lệnh khác được đặt trong đó /bin, mà không đủ điều kiện tên của chúng. Ví dụ: bạn có thể chạy chmodthay vì /bin/chmod. trong rc.local, tôi đã sử dụng tên đủ điều kiện cho tất cả các lệnh ngoại trừ sh, bất cứ khi nào tôi đề nghị bạn chạy chúng.)

Chúng tôi có thể khá chắc chắn /bin/chmod +x /script.shkhông bao giờ chạy (hoặc bạn sẽ thấy điều đó /script.shđã được thực thi). Và chúng tôi biết sh /script.shcũng không chạy.

Nhưng nó đã tải xuống /script.sh. Nó đã thành công! Làm thế nào nó có thể thất bại?

Hai ý nghĩa của thành công

Có hai điều khác nhau mà một người có thể có nghĩa là khi họ nói một lệnh thành công:

  1. Nó đã làm những gì bạn muốn nó làm.
  2. Nó báo cáo rằng nó đã thành công.

Và đó là cho sự thất bại. Khi một người nói một lệnh thất bại, nó có thể có nghĩa là:

  1. Nó không làm những gì bạn muốn nó làm.
  2. Nó báo cáo rằng nó đã thất bại.

Một tập lệnh chạy với sh -e, như rc.local, sẽ dừng chạy lần đầu tiên khi một lệnh báo cáo rằng nó thất bại . Nó không tạo ra sự khác biệt những gì lệnh thực sự đã làm.

Trừ khi bạn có ý định startup_script.shbáo cáo thất bại khi nó làm những gì bạn muốn, đây là một lỗi trong startup_script.sh.

  • Một số lỗi ngăn tập lệnh thực hiện những gì bạn muốn nó làm. Chúng ảnh hưởng đến những gì lập trình viên gọi là tác dụng phụ của nó .
  • Và một số lỗi ngăn tập lệnh báo cáo chính xác cho dù nó có thành công hay không. Chúng ảnh hưởng đến những gì lập trình viên gọi giá trị trả về của nó (trong trường hợp này là trạng thái thoát ).

Rất có thể nó startup_script.shđã làm mọi thứ nó nên, ngoại trừ báo cáo rằng nó thất bại.

Báo cáo thành công hay thất bại như thế nào

Một kịch bản là một danh sách các lệnh không hoặc nhiều hơn. Mỗi lệnh có một trạng thái thoát. Giả sử không có lỗi trong việc thực sự chạy tập lệnh (ví dụ: nếu trình thông dịch không thể đọc dòng tiếp theo của tập lệnh trong khi chạy tập lệnh), trạng thái thoát của tập lệnh là:

  • 0 (thành công) nếu tập lệnh trống (nghĩa là không có lệnh).
  • N, nếu tập lệnh kết thúc là kết quả của lệnh , thì một số mã thoát.exit NN
  • Mã thoát của lệnh cuối cùng chạy trong tập lệnh, nếu không.

Khi một tệp thực thi chạy, nó báo cáo mã thoát riêng - chúng không chỉ dành cho các tập lệnh. (Và về mặt kỹ thuật, mã thoát khỏi tập lệnh là mã thoát được trả về bởi trình bao chạy chúng.)

Ví dụ, nếu một chương trình C kết thúc bằng exit(0);hoặc return 0;trong main()chức năng của nó , mã 0được trao cho hệ điều hành, nó cung cấp cho quá trình gọi (ví dụ, có thể là vỏ mà chương trình được chạy).

0có nghĩa là chương trình đã thành công Mỗi số khác có nghĩa là nó thất bại. (Bằng cách này, các số khác nhau đôi khi có thể đề cập đến các lý do khác nhau khiến chương trình không thành công.)

Lệnh để thất bại

Đôi khi, bạn chạy một chương trình với ý định rằng nó sẽ thất bại. Trong những tình huống này, bạn có thể nghĩ rằng thất bại của nó là thành công, mặc dù đó không phải là lỗi mà chương trình báo cáo thất bại. Ví dụ: bạn có thể sử dụng rmtrên một tệp mà bạn nghi ngờ không tồn tại, chỉ để đảm bảo rằng nó đã bị xóa.

Một cái gì đó như thế này có thể xảy ra startup_script.sh, ngay trước khi nó ngừng chạy. Lệnh cuối cùng để chạy trong tập lệnh có thể là báo cáo lỗi (mặc dù "lỗi" của nó có thể hoàn toàn tốt hoặc thậm chí là cần thiết), khiến báo cáo tập lệnh thất bại.

Thử nghiệm thất bại

Một loại lệnh đặc biệt là một bài kiểm tra , theo ý tôi là một lệnh chạy cho giá trị trả về của nó chứ không phải là tác dụng phụ của nó. Đó là, một bài kiểm tra là một lệnh được chạy để có thể kiểm tra trạng thái thoát của nó (và được thực hiện).

Ví dụ: giả sử tôi quên nếu 4 bằng 5. May mắn thay, tôi biết kịch bản shell:

if [ 4 -eq 5 ]; then
    echo "Yeah, they're totally the same."
fi

Ở đây, thử nghiệm [ -eq 5 ]thất bại vì cuối cùng nó chỉ ra 4 ≠ 5. Điều đó không có nghĩa là bài kiểm tra đã không thực hiện chính xác; nó đã làm. Công việc của bạn là kiểm tra xem 4 = 5, sau đó báo cáo thành công nếu có và thất bại nếu không.

Bạn thấy, trong kịch bản shell, thành công cũng có thể có nghĩa là đúng và thất bại cũng có thể có nghĩa là sai .

Mặc dù echotuyên bố không bao giờ chạy, iftoàn bộ khối sẽ trả lại thành công.

Tuy nhiên, giả sử tôi đã viết nó ngắn hơn:

[ 4 -eq 5 ] && echo "Yeah, they're totally the same."

Đây là tốc ký phổ biến. &&là một boolean toán tử. Một &&biểu thức, bao gồm các &&câu lệnh ở hai bên, trả về false (thất bại) trừ khi cả hai bên trả về true (thành công). Giống như một bình thường .

Nếu ai đó hỏi bạn, "Derek có đến trung tâm mua sắm và nghĩ về một con bướm không?" và bạn biết Derek đã không đến trung tâm thương mại, bạn không cần phải bận tâm tìm hiểu xem anh ta có nghĩ đến một con bướm không.

Tương tự, nếu lệnh bên trái của &&fail (false), toàn bộ &&biểu thức ngay lập tức bị lỗi (false). Các tuyên bố ở phía bên phải &&là không bao giờ chạy.

Ở đây, [ 4 -eq 5 ]chạy. Nó "thất bại" (trả về sai). Vì vậy, toàn bộ &&biểu hiện thất bại. echo "Yeah, they're totally the same."không bao giờ chạy Mọi thứ hoạt động như bình thường, nhưng lệnh này báo cáo thất bại (mặc dù ifđiều kiện tương đương khác ở trên báo cáo thành công).

Nếu đó là câu lệnh cuối cùng trong một tập lệnh (và tập lệnh đã nhận được nó, thay vì kết thúc tại một thời điểm nào đó trước tập lệnh đó), toàn bộ tập lệnh sẽ báo cáo thất bại .

Có rất nhiều bài kiểm tra bên cạnh đó. Ví dụ: có các bài kiểm tra với ||("hoặc"). Tuy nhiên, ví dụ trên phải đủ để giải thích các thử nghiệm là gì và giúp bạn có thể sử dụng hiệu quả tài liệu để xác định xem một câu lệnh / lệnh cụ thể có phải là thử nghiệm hay không.

shso với sh -e, xem lại

Kể từ khi các #!dòng (xem thêm câu hỏi này ) ở phía trên cùng của/etc/rc.localsh -e, hệ điều hành chạy kịch bản như thể nó đã được gọi bằng lệnh:

sh -e /etc/rc.local

Ngược lại, các tập lệnh khác của bạn, chẳng hạn như startup_script.sh, chạy mà không có các -elá cờ:

sh /home/incero/startup_script.sh

Do đó, họ tiếp tục chạy ngay cả khi một lệnh trong đó báo cáo lỗi.

Điều này là bình thường và tốt. rc.localnên được gọi vớish -e và hầu hết các tập lệnh khác - bao gồm hầu hết các tập lệnh được chạy bởi rc.local- không nên.

Chỉ cần đảm bảo ghi nhớ sự khác biệt:

  • Kịch bản chạy với sh -e lỗi báo cáo thoát lần đầu tiên khi một lệnh chúng chứa lỗi báo cáo thoát.

    Dường như tập lệnh là một lệnh dài duy nhất bao gồm tất cả các lệnh trong tập lệnh được nối với && toán tử.

  • Kịch bản chạy với sh(không có-e ) tiếp tục chạy cho đến khi chúng nhận được một lệnh kết thúc (thoát ra khỏi chúng) hoặc đến cuối tập lệnh. Thành công hay thất bại của mọi lệnh về cơ bản là không liên quan (trừ khi lệnh tiếp theo kiểm tra nó). Tập lệnh thoát với trạng thái thoát của lệnh chạy cuối cùng.

Giúp cho kịch bản của bạn hiểu rằng đó không phải là một thất bại như vậy

Làm thế nào bạn có thể giữ cho kịch bản của bạn nghĩ rằng nó thất bại khi nó không?

Bạn nhìn vào những gì xảy ra ngay trước khi nó chạy xong.

  1. Nếu một lệnh thất bại khi cần phải thành công, hãy tìm hiểu tại sao và khắc phục sự cố.

  2. Nếu một lệnh thất bại, và đó là điều đúng xảy ra, thì ngăn tình trạng lỗi đó lan truyền.

    • Một cách để giữ trạng thái thất bại khỏi việc truyền bá là chạy một lệnh khác thành công. /bin/truekhông có tác dụng phụ và báo cáo thành công (giống như/bin/false không làm gì và cũng thất bại).

    • Một cách khác là đảm bảo đoạn script được kết thúc bởi exit 0 .

      Điều đó không nhất thiết giống như exit 0ở phần cuối của kịch bản. Ví dụ, có thể có một if-block nơi tập lệnh thoát ra bên trong.

Tốt nhất nên biết nguyên nhân khiến tập lệnh của bạn báo cáo thất bại, trước khi làm cho nó báo cáo thành công. Nếu nó thực sự thất bại theo một cách nào đó (theo nghĩa là không làm những gì bạn muốn nó làm), bạn không thực sự muốn nó báo cáo thành công.

Khắc phục nhanh

Nếu bạn không thể thực hiện startup_script.shbáo cáo thoát thành công, bạn có thể thay đổi lệnh rc.localchạy nó, để lệnh đó báo cáo thành công mặc dù startup_script.shkhông.

Hiện tại bạn có:

sh /home/incero/startup_script.sh

Lệnh này có cùng tác dụng phụ (nghĩa là tác dụng phụ của việc chạy startup_script.sh), nhưng luôn báo cáo thành công:

sh /home/incero/startup_script.sh || /bin/true

Hãy nhớ rằng, tốt hơn là nên biết tại sao startup_script.shbáo cáo thất bại và khắc phục nó.

Cách khắc phục nhanh hoạt động

Đây thực sự là một ví dụ về ||kiểm tra, một hoặc thử nghiệm.

Giả sử bạn hỏi nếu tôi lấy rác ra hay chải con cú. Nếu tôi vứt rác, tôi có thể nói "có", ngay cả khi tôi không nhớ mình có chải con cú hay không.

Lệnh bên trái của ||chạy. Nếu nó thành công (đúng), thì phía bên tay phải không phải chạy. Vì vậy, nếu startup_script.shbáo cáo thành công,true lệnh không bao giờ chạy.

Tuy nhiên, nếu startup_script.shbáo cáo thất bại [Tôi không vứt rác] , thì kết quả của /bin/true [nếu tôi chải con cú] có vấn đề.

/bin/trueluôn trả về thành công (hoặc đúng , như đôi khi chúng ta gọi nó). Do đó, toàn bộ lệnh thành công và lệnh tiếp theo rc.localcó thể chạy.

Một lưu ý bổ sung về thành công / thất bại, đúng / sai, không / không khác.

Hãy bỏ qua điều này. Bạn có thể muốn đọc điều này nếu bạn lập trình bằng nhiều ngôn ngữ, tuy nhiên (không chỉ là kịch bản lệnh shell).

Đó là một điểm gây nhầm lẫn lớn cho các nhà lập trình shell sử dụng các ngôn ngữ lập trình như C và cho các lập trình viên C tiếp nhận kịch bản shell, để khám phá:

  • Trong kịch bản shell:

    1. Giá trị trả về 0có nghĩa là thành công và / hoặc đúng .
    2. Giá trị trả về của một cái gì đó không 0có nghĩa là thất bại và / hoặc sai .
  • Trong lập trình C:

    1. Giá trị trả về 0có nghĩa là sai ..
    2. Giá trị trả về của một cái gì đó không phải 0đúng .
    3. Không có quy tắc đơn giản cho những gì có nghĩa là thành công và những gì có nghĩa là thất bại. Đôi khi0 có nghĩa là thành công, lần khác có nghĩa là thất bại, lần khác có nghĩa là tổng của hai số bạn vừa thêm bằng không. Các giá trị trả về trong lập trình chung được sử dụng để báo hiệu nhiều loại thông tin khác nhau.
    4. Trạng thái thoát số của chương trình sẽ chỉ ra thành công hay thất bại theo các quy tắc cho kịch bản lệnh shell. Điều đó có nghĩa là, mặc dù 0có nghĩa là sai trong chương trình C của bạn, bạn vẫn làm cho chương trình của mình trở lại 0dưới dạng mã thoát của nó nếu bạn muốn nó báo cáo thành công (mà shell sau đó diễn giải là đúng ).

3
wow câu trả lời tuyệt vời! Có rất nhiều thông tin trong đó mà tôi không biết. Trả về 0 ở cuối tập lệnh đầu tiên làm cho phần còn lại của tập lệnh thực thi (tôi có thể nói rằng chmod + x đã chạy). Thật không may, có vẻ như / usr / bin / wget trong tập lệnh của tôi không thể truy xuất trang web để đặt script.sh. Tôi đoán rằng mạng không sẵn sàng vào thời điểm tập lệnh RC.local này thực thi. Tôi có thể đặt lệnh này ở đâu để nó chạy khi mạng khởi động và tự động khởi động?
Lập trình viên

Bây giờ tôi đã 'hack' nó bằng cách chạy sudo visudo, thêm dòng sau: username ALL = (ALL) NOPASSWD: ALL đang chạy chương trình 'ứng dụng khởi động' (lệnh CLI cho việc này là gì?) Và thêm startup_script vào đây, Trong khi startupscript hiện chạy script.sh với lệnh sudo trước để chạy bằng root (không còn cần mật khẩu). Không nghi ngờ gì đây là siêu không an toàn và khủng khiếp, nhưng đây chỉ là cho một đĩa phân phối trực tiếp pxe-boot tùy chỉnh.
Lập trình viên

@ Stu2000 Giả sử incero(tôi cho rằng đó là tên người dùng) dù sao cũng là quản trị viên và do đó được phép chạy bất kỳ lệnh nào dưới dạng root(bằng cách nhập mật khẩu người dùng của riêng họ), điều đó không an toàn và khủng khiếp . Nếu bạn muốn các giải pháp khác, tôi khuyên bạn nên đăng một câu hỏi mới về điều này . Một vấn đề là tại sao các lệnh trong rc.localkhông chạy (và bạn cũng đã kiểm tra xem điều gì sẽ xảy ra nếu bạn khiến chúng tiếp tục chạy). Bây giờ bạn có một vấn đề khác: bạn muốn kết nối mạng máy trước khi rc.localchạy hoặc lên lịch chạy startup_script.shsau.
Eliah Kagan

1
Tôi đã quay trở lại vấn đề này, đã chỉnh sửa RC.local và thấy wget không hoạt động. Thêm /usr/bin/sudo service networking restartvào tập lệnh khởi động trước lệnh wget dẫn đến wget sau đó hoạt động.
Chương trình

3
@EliahKagan câu trả lời thấu đáo tuyệt vời. Tôi hầu như không bắt gặp một tài liệu tốt hơn vềrc.local
souravc

1

Bạn có thể (trong các biến thể Unix tôi sử dụng) ghi đè hành vi mặc định bằng cách thay đổi:

#!/bin/sh -e

Đến:

#!/bin/sh

Khi bắt đầu kịch bản. Cờ "-e" hướng dẫn sh thoát khỏi lỗi đầu tiên. Tên dài của cờ đó là "errexit", do đó, dòng ban đầu tương đương với:

#!/bin/sh --errexit

vâng, loại bỏ -ecác tác phẩm trên jessie raspbian.
Oki Erie Rinaldi

-emột lý do. Tôi sẽ không làm điều đó nếu tôi là bạn. Nhưng tôi không phải là cha mẹ của bạn.
Wyatt8740

-4

thay đổi dòng đầu tiên từ: #!/bin/sh -e thành !/bin/sh -e


3
Cú pháp với #!là đúng. (Ít nhất, #!phần này là chính xác. Và phần còn lại thường hoạt động tốt, vì rc.local.) Xem bài viết nàycâu hỏi này . Dù sao, chào mừng bạn đến hỏi Ubuntu!
Eliah Kagan
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.