Mengapa rc.local tidak menjalankan semua perintah saya, dan apa yang bisa saya lakukan?

35

Saya memiliki rc.localskrip berikut :

#!/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

Baris pertama, startup_script.shsebenarnya mengunduh script.shfile yang /script.shdimaksud di baris ketiga.

Sayangnya, tampaknya skrip tersebut tidak dapat dieksekusi atau dijalankan. Menjalankan rc.localfile secara manual setelah mulai bekerja dengan sempurna. Bisakah chmod tidak dijalankan saat startup atau apa?

Programster
sumber

Jawaban:

70

Anda dapat melewati semua jalan ke Perbaikan Cepat tetapi itu belum tentu pilihan terbaik. Jadi saya sarankan membaca ini dulu.

rc.local tidak mentolerir kesalahan.

rc.localtidak menyediakan cara untuk secara cerdas pulih dari kesalahan. Jika ada perintah yang gagal, itu berhenti berjalan. Baris pertama #!/bin/sh -e,, menyebabkannya dieksekusi di shell yang dipanggil dengan -eflag. The -ebendera adalah apa yang membuat script (dalam hal ini, rc.local) berhenti berjalan pertama kalinya perintah gagal di dalamnya.

Anda ingin rc.localbersikap seperti ini. Jika perintah gagal, Anda tidak ingin melanjutkan dengan apa pun perintah startup lain yang mungkin bergantung padanya telah berhasil.

Jadi jika ada perintah gagal, perintah selanjutnya tidak akan berjalan. Masalahnya di sini adalah bahwa /script.shtidak berjalan (bukan itu gagal, lihat di bawah), jadi kemungkinan besar beberapa perintah sebelum gagal. Tapi yang mana?

Apakah itu /bin/chmod +x /script.sh?

Tidak.

chmodberjalan dengan baik setiap saat. Asalkan sistem file yang berisi /binsudah dipasang, Anda dapat menjalankan /bin/chmod. Dan /binsudah terpasang sebelum rc.localberjalan.

Saat dijalankan sebagai root, /bin/chmodjarang gagal. Ini akan gagal jika file yang dioperasikan itu hanya-baca, dan mungkin gagal jika sistem file yang aktif tidak mendukung izin. Tidak ada kemungkinan di sini.

Ngomong-ngomong, sh -eadalah satu - satunya alasan itu sebenarnya akan menjadi masalah jika chmodgagal. Saat Anda menjalankan file skrip dengan secara eksplisit memanggil penerjemahnya, tidak masalah jika file tersebut ditandai dapat dieksekusi. Hanya jika dikatakan /script.shbit file yang dapat dieksekusi. Karena dikatakan sh /script.sh, tidak (kecuali tentu saja /script.sh memanggil dirinya sendiri saat berjalan, yang bisa gagal karena tidak dapat dieksekusi, tetapi tidak mungkin itu menyebut dirinya).

Jadi apa yang gagal?

sh /home/incero/startup_script.shgagal. Hampir pasti.

Kami tahu itu berjalan, karena itu diunduh /script.sh.

(Kalau tidak, akan penting untuk memastikan itu berjalan, kalau-kalau entah bagaimana /bintidak di PATH - rc.localtidak harus memiliki yang sama PATHseperti yang Anda miliki ketika Anda login. Jika /bintidak di rc.localjalur, ini akan perlu shdijalankan sebagai /bin/sh. Karena itu berjalan, /binadalah di PATH, yang berarti Anda dapat menjalankan perintah lain yang berada di /bin, tanpa sepenuhnya memenuhi syarat nama mereka. Misalnya, Anda dapat menjalankan hanya chmoddaripada /bin/chmod. Namun, sesuai dengan gaya Anda di rc.local, saya telah menggunakan nama yang sepenuhnya memenuhi syarat untuk semua perintah kecuali sh, setiap kali saya menyarankan Anda menjalankannya.)

Kita bisa yakin /bin/chmod +x /script.shtidak pernah berlari (atau Anda akan melihat bahwa /script.shitu dieksekusi). Dan kita tahu sh /script.shjuga tidak lari.

Tapi itu diunduh /script.sh. Berhasil! Bagaimana itu bisa gagal?

Dua Makna Sukses

Ada dua hal berbeda yang mungkin seseorang maksudkan ketika dia mengatakan sebuah perintah berhasil:

  1. Itu melakukan apa yang Anda inginkan.
  2. Dilaporkan bahwa itu berhasil.

Demikian juga untuk kegagalan. Ketika seseorang mengatakan sebuah perintah gagal, itu bisa berarti:

  1. Itu tidak melakukan apa yang Anda inginkan.
  2. Dilaporkan bahwa itu gagal.

Sebuah skrip yang dijalankan dengan sh -e, seperti rc.local, akan berhenti berjalan saat sebuah perintah melaporkan bahwa ia gagal . Tidak ada bedanya apa yang sebenarnya dilakukan perintah.

Kecuali Anda bermaksud startup_script.shmelaporkan kegagalan saat melakukan apa yang Anda inginkan, ini adalah bug di startup_script.sh.

  • Beberapa bug mencegah skrip melakukan apa yang Anda inginkan. Mereka mempengaruhi apa yang pemrogram sebut efek sampingnya .
  • Dan beberapa bug mencegah skrip melaporkan dengan benar apakah berhasil atau tidak. Mereka memengaruhi apa yang oleh pemrogram sebut nilai pengembaliannya (yang dalam hal ini adalah status keluar ).

Kemungkinan besar startup_script.shmelakukan semua yang seharusnya, kecuali melaporkan bahwa itu gagal.

Bagaimana Keberhasilan atau Kegagalan Dilaporkan

Skrip adalah daftar nol atau lebih perintah. Setiap perintah memiliki status keluar. Dengan asumsi tidak ada kegagalan dalam menjalankan skrip (misalnya, jika penerjemah tidak dapat membaca baris skrip berikutnya saat menjalankan skrip), status keluar dari skrip adalah:

  • 0 (berhasil) jika skrip kosong (mis., tidak punya perintah).
  • N, jika skrip berakhir sebagai hasil dari perintah , di mana ada beberapa kode keluar.exit NN
  • Kode keluar dari perintah terakhir yang dijalankan dalam skrip, jika tidak.

Ketika sebuah executable berjalan, ia melaporkan kode keluarnya sendiri - itu bukan hanya untuk skrip. (Dan secara teknis, kode keluar dari skrip adalah kode keluar yang dikembalikan oleh shell yang menjalankannya.)

Sebagai contoh, jika suatu program C diakhiri dengan exit(0);, atau return 0;dalam main()fungsinya, kode 0tersebut diberikan kepada sistem operasi, yang menyediakannya untuk proses pemanggilan (yang mungkin, misalnya, adalah cangkang dari mana program dijalankan).

0berarti program berhasil. Setiap angka lainnya berarti gagal. (Dengan cara ini, angka-angka yang berbeda kadang-kadang dapat merujuk pada alasan berbeda mengapa program gagal.)

Perintah Dimaksudkan untuk Gagal

Kadang-kadang, Anda menjalankan program dengan maksud bahwa itu akan gagal. Dalam situasi ini, Anda mungkin menganggap kegagalannya sebagai keberhasilan, meskipun itu bukan bug yang dilaporkan kegagalan program. Misalnya, Anda dapat menggunakan rmfile yang Anda curigai tidak ada, hanya untuk memastikan itu dihapus.

Sesuatu seperti ini mungkin terjadi startup_script.sh, tepat sebelum berhenti berjalan. The perintah terakhir untuk dijalankan dalam script mungkin melaporkan kegagalan (meskipun yang "gagal" mungkin benar-benar baik atau bahkan perlu), yang membuat kegagalan laporan skrip.

Tes Dimaksudkan untuk Gagal

Salah satu jenis perintah khusus adalah tes , yang saya maksudkan perintah dijalankan untuk nilai kembalinya daripada efek sampingnya. Artinya, tes adalah perintah yang dijalankan sehingga status keluarnya dapat diperiksa (dan ditindaklanjuti).

Misalnya, saya lupa jika 4 sama dengan 5. Untungnya, saya tahu skrip shell:

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

Di sini, tes [ -eq 5 ]gagal karena ternyata 4 ≠ 5 setelah semua. Itu tidak berarti tes tidak dilakukan dengan benar; itu benar. Tugasnya adalah memeriksa apakah 4 = 5, lalu melaporkan keberhasilan jika demikian, dan gagal jika tidak.

Anda lihat, dalam skrip shell, kesuksesan juga bisa berarti benar , dan kegagalan juga bisa berarti salah .

Meskipun echopernyataan itu tidak pernah berjalan, ifblok secara keseluruhan tidak mengembalikan kesuksesan.

Namun, seharusnya saya menulisnya lebih pendek:

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

Ini adalah istilah umum. &&adalah boolean dan operator. Sebuah &&ekspresi, yang terdiri dari &&dengan pernyataan di kedua sisi, kembali palsu (kegagalan) kecuali kedua belah pihak kembali benar (sukses). Sama seperti normal dan .

Jika seseorang bertanya kepada Anda, "apakah Derek pergi ke mal dan berpikir tentang kupu-kupu?" dan Anda tahu Derek tidak pergi ke mal, Anda tidak perlu repot mencari tahu jika dia memikirkan seekor kupu-kupu.

Begitu pula jika perintah di sebelah kiri &&gagal (false), seluruh &&ekspresi langsung gagal (false). Pernyataan di sisi kanan &&tidak pernah berjalan.

Di sini, [ 4 -eq 5 ]berjalan. Itu "gagal" (return false). Jadi seluruh &&ekspresi gagal. echo "Yeah, they're totally the same."tidak pernah berlari. Segala sesuatu berperilaku sebagaimana mestinya, tetapi perintah ini melaporkan kegagalan (meskipun persyaratan yang setara di ifatas melaporkan keberhasilan).

Jika itu adalah pernyataan terakhir dalam sebuah skrip (dan skrip itu sampai ke sana, daripada berhenti di beberapa titik sebelum itu), seluruh skrip akan melaporkan kegagalan .

Ada banyak tes selain ini. Misalnya, ada tes dengan ||("atau"). Namun, contoh di atas harus cukup untuk menjelaskan tes apa, dan memungkinkan Anda untuk secara efektif menggunakan dokumentasi untuk menentukan apakah pernyataan / perintah tertentu adalah tes.

shvs. sh -e, Ditinjau kembali

Sejak itu #!baris (lihat juga pertanyaan ini ) di bagian atas /etc/rc.localmemiliki sh -e, sistem operasi berjalan script seolah-olah itu yang dipanggil dengan perintah:

sh -e /etc/rc.local

Sebaliknya, skrip Anda yang lain, seperti startup_script.sh , berjalan tanpa itu -ebendera:

sh /home/incero/startup_script.sh

Akibatnya mereka tetap berjalan bahkan ketika sebuah perintah di dalamnya melaporkan kegagalan.

Ini normal dan bagus. rc.localharus dipanggil dengan sh -edan sebagian besar skrip lainnya - termasuk sebagian besar skrip dijalankan oleh - rc.localseharusnya tidak.

Pastikan untuk mengingat perbedaannya:

  • Skrip dijalankan dengan sh -ekegagalan pelaporan yang keluar pertama kali sebuah perintah yang berisi kegagalan pelaporan.

    Seolah-olah skrip adalah perintah panjang tunggal yang terdiri dari semua perintah dalam skrip yang digabungkan && operator.

  • Skrip dijalankan dengan sh(tanpa -e) terus berjalan sampai mereka mendapatkan perintah yang mengakhiri (keluar dari) mereka, atau sampai akhir skrip. Keberhasilan atau kegagalan setiap perintah pada dasarnya tidak relevan (kecuali perintah berikutnya memeriksanya). Script keluar dengan status keluar dari menjalankan perintah terakhir.

Setelah Membantu Skrip Anda Memahami Ini Bukan Kegagalan

Bagaimana Anda bisa menjaga agar skrip Anda tidak gagal ketika gagal?

Anda melihat apa yang terjadi sebelum itu selesai berlari.

  1. Jika suatu perintah gagal ketika seharusnya berhasil, cari tahu mengapa, dan perbaiki masalahnya.

  2. Jika suatu perintah gagal, dan itu adalah hal yang benar terjadi, maka cegah status kegagalan itu agar tidak menyebar.

    • Salah satu cara untuk menjaga agar status kegagalan tidak diperbanyak adalah dengan menjalankan perintah lain yang berhasil. /bin/truetidak memiliki efek samping dan melaporkan keberhasilan (seperti halnya/bin/false tidak melakukan apa pun dan juga gagal).

    • Cara lain adalah memastikan skrip diakhiri oleh exit 0 .

      Itu belum tentu sama dengan exit 0berada di akhir skrip. Misalnya, mungkin ada ifblok-di mana skrip keluar di dalamnya.

Yang terbaik untuk mengetahui apa yang menyebabkan skrip Anda melaporkan kegagalan, sebelum membuatnya melaporkan keberhasilan. Jika itu benar-benar gagal dalam beberapa hal (dalam arti tidak melakukan apa yang Anda inginkan), Anda tidak benar-benar ingin melaporkan kesuksesan.

Perbaikan Cepat

Jika Anda tidak dapat membuat startup_script.shkeluar pelaporan berhasil, Anda dapat mengubah perintah rc.localyang menjalankannya, sehingga perintah melaporkan keberhasilan meskipun startup_script.shtidak.

Saat ini Anda memiliki:

sh /home/incero/startup_script.sh

Perintah ini memiliki efek samping yang sama (yaitu, efek samping dari menjalankan startup_script.sh), tetapi selalu melaporkan keberhasilan:

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

Ingat, lebih baik untuk mengetahui mengapa startup_script.shmelaporkan kegagalan, dan memperbaikinya.

Cara Kerja Perbaikan Cepat

Ini sebenarnya adalah contoh dari ||tes, tes atau .

Misalkan Anda bertanya apakah saya mengeluarkan sampah atau menyikat burung hantu. Jika saya mengeluarkan sampah, saya bisa jujur ​​mengatakan "ya," bahkan jika saya tidak ingat apakah saya menyapu burung hantu atau tidak.

Perintah di sebelah kiri ||menjalankan. Jika berhasil (benar), maka sisi kanan tidak harus berjalan. Jadi jika startup_script.shlaporan berhasil, makatrue perintah tidak pernah berjalan.

Namun, jika startup_script.shmelaporkan kegagalan [saya tidak mengeluarkan sampah] , maka hasil dari /bin/true [jika saya menyikat burung hantu] penting.

/bin/trueselalu mengembalikan kesuksesan (atau benar , seperti yang kadang-kadang kita sebut itu). Akibatnya, seluruh perintah berhasil, dan perintah selanjutnya rc.localdapat dijalankan.

Catatan tambahan tentang keberhasilan / kegagalan, benar / salah, nol / bukan nol.

Jangan ragu untuk mengabaikan ini. Anda mungkin ingin membaca ini jika Anda memprogram dalam lebih dari satu bahasa (yaitu, bukan hanya shell scripting)

Ini adalah titik kebingungan besar bagi skrip shell yang menggunakan bahasa pemrograman seperti C, dan bagi programmer C yang menggunakan skrip shell, untuk menemukan:

  • Dalam skrip shell:

    1. Nilai pengembalian 0berarti kesuksesan dan / atau benar .
    2. Nilai pengembalian sesuatu selain 0berarti kegagalan dan / atau salah .
  • Dalam pemrograman C:

    1. Nilai pengembalian 0berarti false ..
    2. Nilai pengembalian sesuatu selain 0berarti benar .
    3. Tidak ada aturan sederhana untuk apa yang berarti kesuksesan dan apa yang berarti kegagalan. Terkadang0 berarti sukses, di lain waktu itu berarti kegagalan, di lain waktu itu berarti jumlah dari dua angka yang baru Anda tambahkan adalah nol. Nilai pengembalian dalam pemrograman umum digunakan untuk memberi sinyal berbagai macam jenis informasi.
    4. Status keluar numerik suatu program harus menunjukkan keberhasilan atau kegagalan sesuai dengan aturan untuk skrip shell. Artinya, meskipun 0berarti salah dalam program C Anda, Anda masih membuat program Anda kembali 0sebagai kode keluarnya jika Anda ingin melaporkannya berhasil (yang shell kemudian tafsirkan sebagai benar ).
Eliah Kagan
sumber
3
wow jawaban yang bagus! Ada banyak info di sana yang saya tidak tahu. Mengembalikan 0 pada akhir skrip pertama menyebabkan sisa skrip dieksekusi (well, saya dapat mengatakan bahwa chmod + x ran). Sayangnya sepertinya / usr / bin / wget dalam skrip saya gagal untuk mengambil halaman web untuk dimasukkan ke dalam script.sh. Saya menduga bahwa jaringan tidak siap pada saat skrip rc.local ini dijalankan. Di mana saya dapat menempatkan perintah ini sehingga akan berjalan ketika jaringan telah dimulai, dan secara otomatis pada saat startup?
Programster
Saya telah 'meretas' untuk saat ini dengan menjalankan sudo visudo, menambahkan baris berikut: username ALL = (ALL) NOPASSWD: SEMUA menjalankan program 'aplikasi startup' (apa perintah CLI untuk ini?), Dan menambahkan startup_script untuk ini, sementara startupscript sekarang menjalankan script.sh dengan perintah sudo sebelumnya untuk dijalankan sebagai root (tidak perlu lagi kata sandi). Tidak diragukan lagi ini super tidak aman dan mengerikan, tetapi ini hanya untuk disc live distro pxe-custom.
Programster
@ Stu2000 Dengan asumsi incero(saya berasumsi itu nama pengguna) adalah administrator , dan dengan demikian diizinkan untuk menjalankan perintah apa pun root(dengan memasukkan kata sandi pengguna mereka sendiri), itu tidak super tidak aman dan mengerikan . Jika Anda menginginkan solusi lain, saya sarankan memposting pertanyaan baru tentang ini . Satu masalah adalah mengapa perintah di rc.localtidak berjalan (dan Anda juga memeriksa untuk melihat apa yang akan terjadi jika Anda membuatnya terus berjalan). Sekarang Anda memiliki masalah yang berbeda: Anda ingin agar mesin terhubung dengan jaringan sebelum rc.localdijalankan, atau menjadwalkan pengoperasian startup_script.shnanti.
Eliah Kagan
1
Sejak itu saya kembali ke masalah ini, mengedit rc.local dan menemukan bahwa wget tidak 'berfungsi'. Menambahkan /usr/bin/sudo service networking restartke skrip startup sebelum perintah wget menghasilkan wget kemudian berfungsi.
Programster
3
@EliahKagan jawaban lengkap yang luar biasa. Saya hampir tidak menemukan dokumentasi yang lebih baik padarc.local
souravc
1

Anda dapat (dalam varian Unix yang saya gunakan) mengesampingkan perilaku default dengan mengubah:

#!/bin/sh -e

Untuk:

#!/bin/sh

Di awal skrip. Bendera "-e" memerintahkan sh untuk keluar pada kesalahan pertama. Nama panjang bendera itu adalah "errexit", jadi garis aslinya setara dengan:

#!/bin/sh --errexit
Bryce
sumber
ya, menghapus -ekarya pada jessie raspbian.
Oki Erie Rinaldi
Ada di -esana karena suatu alasan. Aku tidak akan melakukan itu jika aku jadi kamu. Tapi aku bukan orang tuamu.
Wyatt8740
-4

ubah baris pertama dari: #!/bin/sh -e ke !/bin/sh -e

elJenso
sumber
3
Sintaks dengan #!adalah yang benar. (Setidaknya, #!bagian itu benar. Dan sisanya biasanya berfungsi dengan baik untuk, untuk rc.local.) Lihat artikel ini dan pertanyaan ini . Bagaimanapun, selamat datang di Tanya Ubuntu!
Eliah Kagan