skrip bash: hasil yang berbeda saat dipanggil dengan atau tanpa sudo

10

Di Ubuntu 16.04.3, saya memiliki skrip bash yang sangat sederhana:

test.sh

[[ 0 == 0 ]] && result="true" || result="false"
echo $result
echo $USER $SHELL $0

Ketika saya menyebutnya sebagai pengguna non-root meatau sebagai root, itu berfungsi seperti yang diharapkan. Jika saya menggunakan sudo ./test.sh, itu mengeluh tentang kesalahan sintaksis:

$ ./test.sh
true
me /bin/bash ./test.sh

$ sudo su
# ./test.sh 
true
root /bin/bash ./test.sh

# exit
$ sudo ./test.sh
./test.sh: 1: ./test.sh: [[: not found
false
root /bin/bash ./test.sh

Apa yang menyebabkan ini? Bagaimana saya bisa memperbaikinya sehingga medapat menggunakan skrip ini dengan normal dan dengan sudo?

James Newton
sumber
3
Kiat pro: tidak ada gunanya berlarisudo su . Jalankan saja sudo -iatau sudo -ssebaliknya.
terdon
@terdon sudo -imengubah lokasi menjadi /root. sudo suatau sudo -sjangan ubah lokasi direktori.
James Newton
Ya, baca pertanyaan yang saya tautkan sebelumnya untuk alasannya. Dan maaf, saya sudah mengedit komentar saya sebelumnya, saya lupa menyebutkan -s.
terdon

Jawaban:

20

Setiap skrip dimulai dengan Shebang , tanpa itu skrip memulai skrip Anda tidak tahu penerjemah mana yang harus menjalankan skrip 1 Anda dan mungkin - seperti dalam kasus di sudo ./script.shsini - jalankan dengan sh, yang di Ubuntu 16.04 ditautkan dash. The ekspresi kondisional [[ adalah bashperintah senyawa , sehingga dashtidak tahu bagaimana menanganinya dan melempar kesalahan Anda temui.

Solusinya di sini adalah menambahkan

#!/bin/bash

sebagai baris pertama skrip Anda. Anda mungkin mendapatkan hasil yang sama ketika Anda menyebutnya secara eksplisit dengan sudo bash ./script.sh, tetapi shebang adalah cara untuk pergi.
Untuk memeriksa shell mana yang menjalankan skrip Anda, tambahkan echo $0ke dalamnya. Itu tidak sama dengan echo $SHELL , mengutip wiki.archlinux.org :

SHELL berisi path ke shell yang disukai pengguna. Perhatikan bahwa ini belum tentu shell yang sedang berjalan, meskipun Bash menetapkan variabel ini saat startup.

1: Ketika Anda mulai ./test.shdengan bashasumsi bash, hal yang sama berlaku untuk sudo susubkulit.

pencuci mulut
sumber
1
Perhatikan juga bahwa bash menjalankan skrip tanpa shebang menggunakan bash, tidak /bin/sh.
muru
@ Ddit Memperbaikinya. Terima kasih! Bagaimana saya bisa mengecek dari dalam skrip mana shell menjalankannya? ( echo $0Memberi saya nama script: ./test.sh)
James Newton
@ JamesNewton tidak ada cara portabel, AFAIK, tetapi Anda dapat memeriksa /proc/$$/exepoin apa . Anda juga dapat menguji berbagai variabel seperti $BASH_VERSION,, $ZSH_VERSIONdll. (Tetapi tanda hubung tidak mengatur variabel seperti itu)
muru
5

Seperti yang dijelaskan oleh @dabut , masalahnya di sini adalah skrip Anda tidak memiliki garis shebang . Tanpa shebang, sudoakan default untuk mencoba menjalankan file menggunakan /bin/sh. Saya tidak dapat menemukannya didokumentasikan di mana pun, tetapi saya mengonfirmasi dengan memeriksa sudokode sumber tempat saya menemukan yang berikut dalam file pathnames.h:

#ifndef _PATH_BSHELL
#define _PATH_BSHELL "/bin/sh"
#endif /* _PATH_BSHELL */

Ini berarti "atur jika variabel _PATH_BSHELLtidak ditentukan, atur ke /bin/sh". Kemudian, dalam configureskrip yang disertakan dalam tarball sumber, kami memiliki:

for p in "/bin/bash" "/usr/bin/sh" "/sbin/sh" "/usr/sbin/sh" "/bin/ksh" "/usr/bin/ksh" "/bin/bash" "/usr/bin/bash"; do
    if test -f "$p"; then
    found=yes
    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $p" >&5
$as_echo "$p" >&6; }
    cat >>confdefs.h <<EOF
#define _PATH_BSHELL "$p"
EOF

    break
    fi
done

Lingkaran ini akan mencari /bin/bash, /usr/bin/sh, /sbin/sh, /usr/sbin/shatau /bin/kshdan kemudian menetapkan _PATH_BSHELLuntuk mana ditemukan pertama kali . Karena /bin/shitu yang pertama dalam daftar dan itu ada, _PATH_BSHELLdiatur ke /bin/sh. Hasil dari semua ini adalah bahwa shell default sudokecuali ditentukan lain /bin/sh.

Jadi, sudoakan secara default menjalankan hal-hal menggunakan /bin/shdan, pada Ubuntu, yang merupakan symlink ke dash, shell minimal yang sesuai dengan POSIX:

$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Feb 27  2015 /bin/sh -> dash

The [[konstruk fitur bash, itu tidak didefinisikan oleh standar POSIX dan tidak dimengerti oleh dash:

$ bash -c '[[ true ]] && echo yes'
yes
$ dash -c '[[ true ]] && echo yes'
dash: 1: [[: not found

Secara terperinci, dalam tiga doa Anda mencoba:

  1. ./test.sh

    Tidak ada sudo; tanpa adanya garis shebang, shell Anda akan mencoba untuk mengeksekusi file itu sendiri. Karena Anda menjalankan bash, ini akan berjalan bash ./test.shdan bekerja secara efektif .

  2. sudo sudiikuti oleh ./test.sh.

    Di sini, Anda memulai shell baru untuk pengguna root. Ini akan menjadi apa pun shell yang didefinisikan dalam $SHELLvariabel lingkungan untuk pengguna itu dan, pada Ubuntu, shell default root adalah bash:

    $ grep root /etc/passwd
    root:x:0:0:root:/root:/bin/bash
  3. sudo ./test.sh

    Di sini, Anda membiarkan sudomenjalankan perintah secara langsung. Karena shell defaultnya /bin/shseperti dijelaskan di atas, ini menyebabkannya menjalankan skrip dengan /bin/sh, yang mana dashdan gagal karena dashtidak mengerti [[.


Catatan : perincian tentang bagaimana sudomenyetel shell default tampaknya sedikit lebih rumit. Saya mencoba mengubah file yang disebutkan dalam jawaban saya untuk menunjuk /bin/bashtetapi sudomasih default ke /bin/sh. Jadi harus ada beberapa tempat lain dalam kode sumber di mana shell default didefinisikan. Namun demikian, poin utama (yang sudodefaultnya sh) masih berlaku.

terdon
sumber
Saya tidak dapat menemukannya didokumentasikan di mana pun - saya juga, saya hanya berasumsi itu akan digunakan /bin/shdari pesan kesalahan - apa lagi yang mungkin terjadi? Pertanyaannya dijawab dengan indah di shell apa yang digunakan sudo · SO , lihat juga man sudo, bagian COMMAND EXECUTION . Ternyata sudotidak menggunakan shell perantara !
hidangan penutup
1
@D ya, itu menggunakan implementasi sendiri dari execvepanggilan sistem yang secara default sh. Dan tidak, shell perantara tidak relevan, ini bukan tentang shell yang menjalankan perintah tetapi tentang juru shell yang digunakan untuk membaca skrip shell yang diberikan. Jadi tidak, ia tidak meluncurkan shell perantara tetapi masih membutuhkan shell interpreter untuk skrip shell.
terdon