Apa perbedaan antara mengeksekusi skrip shell menggunakan "source file.sh", "./file.sh", "sh file.sh", ". ./file.sh ”?

13

Lihat kode:

#!/bin/bash
read -p "Eneter 1 for UID and 2 for LOGNAME" choice
if [ $choice -eq 1 ]
then
        read -p "Enter UID:  " uid
        logname=`cat /etc/passwd | grep $uid | cut -f1 -d:`
else
        read -p "Enter Logname:  " logname
fi
not=`ps -au$logname | grep -c bash`
echo  "The number of terminals opened by $logname are $not"

Kode ini digunakan untuk mengetahui jumlah terminal yang dibuka oleh pengguna pada PC yang sama. Sekarang ada dua pengguna yang masuk, misalkan x dan y. Saya saat ini login sebagai y dan ada 3 terminal terbuka di pengguna x. Jika saya menjalankan kode ini di y menggunakan cara yang berbeda seperti yang disebutkan di atas hasilnya adalah:

$ ./file.sh
The number of terminals opened by x are 3

$ bash file.sh
The number of terminals opened by x are 5

$ sh file.sh
The number of terminals opened by x are 3

$ source file.sh
The number of terminals opened by x are 4

$ . ./file.sh
The number of terminals opened by x are 4

Catatan: Saya melewati 1 dan 1000 uid untuk semua executable ini.

Sekarang bisakah Anda menjelaskan perbedaan di antara semua ini?

Ramana Reddy
sumber
perbedaannya adalah shell mana yang dieksekusi. sh bukanlah bash
j0h
2
Dua eksekusi terakhir juga berbeda karena Anda mengeksekusi dalam konteks yang sama. Lebih lanjut di sini
Zaka Elab
Saya mencoba menghitung jumlah bash instance (di sini sama dengan no. Terminal) yang dibuka oleh pengguna lain (bukan pengguna yang sama dengan tempat kami masuk) dan dapatkah Anda menjelaskan mengapa ada beberapa nomor yang berbeda dalam setiap kasus
Ramana Reddy
@RamanaReddy pengguna lain mungkin telah menjalankan skrip atau memulai tab baru. Siapa tahu?
muru

Jawaban:

21

Satu-satunya perbedaan utama adalah antara sumber dan pelaksanaan skrip. source foo.shakan sumber itu dan semua contoh lain yang Anda tunjukkan mengeksekusi. Lebih detail:

  1. ./file.sh

    Ini akan menjalankan skrip bernama file.shyang ada di direktori saat ini ( ./). Biasanya, ketika Anda menjalankan command, shell akan melihat direktori di dalam Anda $PATHuntuk file executable yang disebut command. Jika Anda memberikan path lengkap, seperti /usr/bin/commandatau ./command, maka $PATHdiabaikan dan file tertentu dijalankan.

  2. ../file.sh

    Ini pada dasarnya sama dengan ./file.shkecuali bahwa alih-alih mencari di direktori saat ini untuk file.sh, itu mencari di direktori induk ( ../).

  3. sh file.sh

    Ini setara dengan sh ./file.sh, seperti di atas akan menjalankan skrip yang disebut file.shdalam direktori saat ini. Perbedaannya adalah Anda menjalankannya secara eksplisit dengan shshell. Pada sistem Ubuntu, itu dashdan tidak bash. Biasanya, skrip memiliki garis shebang yang memberikan program yang seharusnya dijalankan. Memanggil mereka dengan yang berbeda akan menimpanya. Sebagai contoh:

    $ cat foo.sh
    #!/bin/bash  
    ## The above is the shebang line, it points to bash
    ps h -p $$ -o args='' | cut -f1 -d' '  ## This will print the name of the shell

    Script itu hanya akan mencetak nama shell yang digunakan untuk menjalankannya. Mari kita lihat apa yang dikembalikan ketika dipanggil dengan cara yang berbeda:

    $ bash foo.sh
    bash
    $ sh foo.sh 
    sh
    $ zsh foo.sh
    zsh

    Jadi, panggilan memanggil skrip dengan shell scriptakan menimpa garis shebang (jika ada) dan menjalankan skrip dengan shell apa pun yang Anda katakan.

  4. source file.sh atau . file.sh

    Ini disebut, cukup mengejutkan, sumber skrip. Kata kunci sourceadalah alias untuk .perintah builtin shell . Ini adalah cara menjalankan skrip di dalam shell saat ini. Biasanya, ketika sebuah skrip dieksekusi, ia dijalankan di dalam cangkangnya sendiri yang berbeda dari yang sekarang. Menggambarkan:

    $ cat foo.sh
    #!/bin/bash
    foo="Script"
    echo "Foo (script) is $foo"

    Sekarang, jika saya mengatur variabel fooke sesuatu yang lain di shell induk dan kemudian menjalankan skrip, skrip akan mencetak nilai yang berbeda dari foo(karena itu juga diatur dalam skrip) tetapi nilai foodi dalam shell induk tidak akan berubah:

    $ foo="Parent"
    $ bash foo.sh 
    Foo (script) is Script  ## This is the value from the script's shell
    $ echo "$foo"          
    Parent                  ## The value in the parent shell is unchanged

    Namun, jika saya sumber skrip alih-alih menjalankannya, skrip akan dijalankan di shell yang sama sehingga nilai foodi dalam induk akan diubah:

    $ source ./foo.sh 
    Foo (script) is Script   ## The script's foo
    $ echo "$foo" 
    Script                   ## Because the script was sourced, 
                             ## the value in the parent shell has changed

    Jadi, sumber digunakan dalam beberapa kasus di mana Anda ingin skrip memengaruhi shell tempat Anda menjalankannya. Ini biasanya digunakan untuk mendefinisikan variabel shell dan membuatnya tersedia setelah skrip selesai.


Dengan semua itu dalam pikiran, alasan Anda mendapatkan jawaban yang berbeda adalah, pertama-tama, bahwa skrip Anda tidak melakukan apa yang Anda pikirkan. Ini menghitung berapa kali yang bashmuncul di output ps. Ini bukan jumlah terminal terbuka , ini adalah jumlah cangkang yang berjalan (pada kenyataannya, bahkan bukan itu, tapi itu diskusi lain). Untuk memperjelas, saya sedikit menyederhanakan skrip Anda untuk ini:

#!/bin/bash
logname=terdon
not=`ps -au$logname | grep -c bash`
echo  "The number of shells opened by $logname is $not"

Dan jalankan dengan berbagai cara hanya dengan satu terminal terbuka:

  1. Peluncuran langsung ./foo.sh,.

    $ ./foo.sh
    The number of shells opened by terdon is 1

    Di sini, Anda menggunakan garis shebang. Ini berarti bahwa skrip dijalankan secara langsung oleh apa pun yang diatur di sana. Ini mempengaruhi cara skrip ditampilkan dalam output dari ps. Alih-alih terdaftar sebagai bash foo.sh, itu hanya akan ditampilkan sebagai foo.shyang berarti bahwa Anda grepakan melewatkannya. Sebenarnya ada 3 instance bash yang berjalan: proses induk, bash menjalankan skrip dan yang lainnya menjalankan psperintah . Yang terakhir ini penting, meluncurkan perintah dengan substitusi perintah ( `command`atau $(command)) menghasilkan salinan shell induk yang diluncurkan dan menjalankan perintah. Di sini, bagaimanapun, tidak ada yang ditampilkan karena cara yang psmenunjukkan hasilnya.

  2. Peluncuran langsung dengan shell eksplisit (bash)

    $ bash foo.sh 
    The number of shells opened by terdon is 3

    Di sini, karena Anda menjalankan bash foo.sh, output dari psakan ditampilkan bash foo.shdan dihitung. Jadi, di sini kita memiliki proses induk, bashskrip yang berjalan, dan shell kloning (running the ps) semua ditampilkan karena sekarang psakan menampilkan masing-masing karena perintah Anda akan memasukkan kata bash.

  3. Peluncuran langsung dengan shell yang berbeda ( sh)

    $ sh foo.sh
    The number of shells opened by terdon is 1

    Ini berbeda karena Anda menjalankan skrip dengan shdan tidak bash. Karena itu, satu-satunya bashcontoh adalah shell induk tempat Anda meluncurkan skrip Anda. Semua shell lain yang disebutkan di atas dijalankan oleh shsebagai gantinya.

  4. Sumber (baik dengan .atau source, hal yang sama)

    $ . ./foo.sh 
    The number of shells opened by terdon is 2

    Seperti yang saya jelaskan di atas, sumber skrip membuatnya berjalan di shell yang sama dengan proses induk. Namun, subkulit terpisah mulai meluncurkan psperintah dan itu menjadikan total menjadi dua.


Sebagai catatan terakhir, cara yang benar untuk menghitung proses yang sedang berjalan bukan untuk menguraikan pstetapi untuk digunakan pgrep. Semua masalah ini akan dihindari seandainya Anda baru saja berlari

pgrep -cu terdon bash

Jadi, versi skrip Anda yang selalu mencetak angka yang tepat adalah (perhatikan tidak adanya substitusi perintah):

#!/usr/bin/env bash
user="terdon"

printf "Open shells:"
pgrep -cu "$user" bash

Itu akan mengembalikan 1 ketika bersumber dan 2 (karena bash baru akan diluncurkan untuk menjalankan skrip) untuk semua cara lain peluncuran. Itu masih akan mengembalikan 1 ketika diluncurkan dengan shkarena proses anak tidak bash.

terdon
sumber
Ketika Anda mengatakan substitusi perintah meluncurkan salinan dari shell induk, bagaimana perbedaan salinan ini dari sub-shell seperti ketika Anda menjalankan skrip dengan ./foo.sh?
Didier A.
Dan ketika Anda menjalankan pgrep tanpa substitusi perintah, saya menganggap itu berjalan dari dalam shell yang sama dengan script berjalan? Begitu mirip dengan sumber?
Didier A.
@idibus Saya tidak yakin apa yang Anda maksud. Substitusi perintah dijalankan dalam sebuah subkulit; ./foo.shberjalan di shell baru yang bukan salinan induknya. Misalnya, jika Anda mengatur foo="bar"di terminal Anda dan kemudian menjalankan skrip yang dijalankan echo $foo, Anda akan mendapatkan baris kosong karena shell skrip tidak akan mewarisi nilai variabel. pgrepadalah biner yang terpisah, dan ya dijalankan oleh skrip yang Anda jalankan.
terdon
Pada dasarnya saya perlu klarifikasi tentang: "perhatikan tidak adanya penggantian perintah". Mengapa menjalankan biner pgrep dari skrip tidak menambahkan shell tambahan, tetapi menjalankan binary ps dengan substitusi perintah tidak? Kedua, saya perlu klarifikasi tentang "salinan shell induk", apakah itu seperti sub-shell di mana variabel shell dari induk disalin ke anak? Mengapa substitusi perintah melakukan itu?
Didier A.