Quali sono le differenze tra l'esecuzione degli script della shell usando “source file.sh”, “./file.sh”, “sh file.sh”, “. ./file.sh”?


13

Dai un'occhiata al codice:

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

Questo codice viene utilizzato per scoprire il numero di terminali aperti da un utente sullo stesso PC. Ora ci sono due utenti connessi, diciamo xey. Sono attualmente connesso come y e ci sono 3 terminali aperti nell'utente x. Se eseguo questo codice in y usando diversi modi come menzionato sopra, i risultati sono:

$ ./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

Nota: ho passato 1 e ho usato 1000 per tutti questi eseguibili.

Ora puoi per favore spiegare le differenze tra tutti questi?


la differenza è quale shell viene eseguita. sh not bash
j0h

2
Anche le ultime due esecuzioni sono diverse perché state eseguendo nello stesso contesto. Altro qui
Zaka Elab,

Sto cercando di contare le istanze bash numeriche (qui equivale a n. Di terminali) aperte dall'altro utente (non lo stesso utente a cui abbiamo effettuato l'accesso) e potresti spiegare perché in ogni caso è arrivato un numero diverso
Ramana Reddy

@RamanaReddy l'altro utente potrebbe aver eseguito uno script o avviato una nuova scheda. Chissà?
Muru,

Risposte:


21

L'unica grande differenza è tra l'approvvigionamento e l'esecuzione di uno script. source foo.shlo procurerà e tutti gli altri esempi che mostri sono in esecuzione. Più in dettaglio:

  1. ./file.sh

    Questo eseguirà uno script chiamato file.shche si trova nella directory corrente ( ./). Normalmente, quando si esegue command, la shell cercherà tra le directory in $PATHun file eseguibile chiamato command. Se si fornisce un percorso completo, come /usr/bin/commando ./command, quindi $PATHviene ignorato e viene eseguito quel file specifico.

  2. ../file.sh

    Questo è fondamentalmente lo stesso di ./file.shtranne che invece di cercare nella directory corrente file.sh, cerca nella directory parent ( ../).

  3. sh file.sh

    Questo equivalente a sh ./file.sh, come sopra, eseguirà lo script chiamato file.shnella directory corrente. La differenza è che lo stai eseguendo esplicitamente con la shshell. Sui sistemi Ubuntu, questo è dashe non bash. Di solito, gli script hanno una riga shebang che fornisce al programma in cui devono essere eseguiti. Chiamandoli con uno diverso si annulla quello. Per esempio:

    $ 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

    Lo script stamperà semplicemente il nome della shell utilizzata per eseguirlo. Vediamo cosa restituisce quando viene chiamato in diversi modi:

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

    Quindi, chiamare chiamando uno script con shell scriptsovrascriverà la linea shebang (se presente) ed eseguirà lo script con qualunque shell tu gli dica.

  4. source file.sh o . file.sh

    Questo si chiama, abbastanza sorprendentemente, approvvigionamento della sceneggiatura. La parola chiave sourceè un alias del .comando incorporato shell . Questo è un modo per eseguire lo script all'interno della shell corrente. Normalmente, quando uno script viene eseguito, viene eseguito nella sua shell che è diversa da quella corrente. Illustrare:

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

    Ora, se imposto la variabile foosu qualcos'altro nella shell genitore e quindi eseguo lo script, lo script stamperà un valore diverso di foo(perché è anche impostato all'interno dello script) ma il valore di foonella shell genitore rimarrà invariato:

    $ 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

    Tuttavia, se provo lo script invece di eseguirlo, verrà eseguito nella stessa shell, quindi il valore di foonel genitore verrà modificato:

    $ 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

    Pertanto, il sourcing viene utilizzato nei pochi casi in cui si desidera che uno script influisca sulla shell da cui lo si esegue. In genere viene utilizzato per definire le variabili della shell e renderle disponibili al termine dello script.


Tenendo presente tutto ciò, il motivo per cui ottieni risposte diverse è, innanzitutto, che la tua sceneggiatura non fa ciò che pensi che faccia. Conta il numero di volte che bashappare nell'output di ps. Questo non è il numero di terminali aperti , è il numero di shell in esecuzione (in realtà, non è nemmeno quello, ma questa è un'altra discussione). Per chiarire, ho semplificato un po 'la tua sceneggiatura a questo:

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

Ed eseguilo in vari modi con un solo terminale aperto:

  1. Lancio diretto, ./foo.sh.

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

    Qui, stai usando la linea shebang. Ciò significa che lo script viene eseguito direttamente da qualsiasi cosa sia impostata lì. Ciò influisce sul modo in cui lo script viene mostrato nell'output di ps. Invece di essere elencato come bash foo.sh, verrà mostrato solo come foo.shciò significa che grepti mancherà. In realtà ci sono 3 istanze di bash in esecuzione: il processo parent, il bash che esegue lo script e un altro che esegue il pscomando . Quest'ultimo è importante, l'avvio di un comando con sostituzione del comando ( `command`o $(command)) comporta l'avvio di una copia della shell padre e l'esecuzione del comando. Qui, tuttavia, nessuno di questi è mostrato a causa del modo in cui psmostra il suo output.

  2. Avvio diretto con shell esplicita (bash)

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

    Qui, poiché stai funzionando con bash foo.sh, l'output di psverrà mostrato bash foo.she conteggiato. Quindi, qui abbiamo il processo genitore, l' bashesecuzione dello script e la shell clonata (in esecuzione ps) tutti mostrati perché ora psli mostrerà ciascuno perché il tuo comando includerà la parola bash.

  3. Avvio diretto con una shell diversa ( sh)

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

    Questo è diverso perché stai eseguendo lo script con she non bash. Pertanto, l'unica bashistanza è la shell padre in cui è stato avviato lo script. shInvece, vengono eseguite tutte le altre shell menzionate sopra .

  4. Sourcing (sia .o source, stessa cosa)

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

    Come ho spiegato sopra, l'approvvigionamento di uno script provoca l'esecuzione nella stessa shell del processo padre. Tuttavia, viene avviata una subshell separata per avviare il pscomando e questo porta il totale a due.


Come nota finale, il modo corretto di contare i processi in esecuzione non è analizzare psma utilizzare pgrep. Tutti questi problemi sarebbero stati evitati se avessi appena corso

pgrep -cu terdon bash

Quindi, una versione funzionante del tuo script che stampa sempre il numero giusto è (nota l'assenza di sostituzione del comando):

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

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

Ciò restituirà 1 quando viene fornito e 2 (perché verrà lanciato un nuovo bash per eseguire lo script) per tutti gli altri modi di avvio. Restituirà comunque 1 all'avvio shpoiché il processo figlio non lo è bash.


Quando dici che la sostituzione dei comandi avvia una copia della shell genitore, in che modo questa copia differisce da una sotto-shell come quando esegui lo script con ./foo.sh?
Didier A.

E quando esegui pgrep senza sostituzione di comando, suppongo che stia funzionando all'interno della stessa shell in cui viene eseguito lo script? Così simile al sourcing?
Didier A.

@didibus Non sono sicuro di cosa intendi. La sostituzione dei comandi viene eseguita in una subshell; ./foo.shviene eseguito in una nuova shell che non è una copia del genitore. Ad esempio, se si imposta foo="bar"nel proprio terminale e quindi si esegue uno script che viene eseguito echo $foo, si otterrà una riga vuota poiché la shell dello script non avrà ereditato il valore della variabile. pgrepè un file binario separato e sì, è eseguito dallo script in esecuzione.
terdon,

Fondamentalmente ho bisogno di un chiarimento su: "notare l'assenza di sostituzione del comando". Perché l'esecuzione del binario pgrep da uno script non aggiunge una shell aggiuntiva, ma l'esecuzione del binario ps con sostituzione comandi lo fa? In secondo luogo, ho bisogno di chiarimenti sulla "copia della shell genitore", è come una sotto-shell in cui le variabili shell del genitore vengono copiate nel figlio? Perché la sostituzione dei comandi lo fa?
Didier A.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.