Dopo tutto, c'è una differenza tra "." E "source" in bash?


38

Stavo cercando la differenza tra il "." e i comandi integrati "source" e alcune fonti (ad esempio, in questa discussione, e la manpage bash ) suggeriscono che questi sono gli stessi.

Tuttavia, a seguito di un problema con le variabili di ambiente, ho condotto un test. Ho creato un file testenv.shche contiene:

#!/bin/bash
echo $MY_VAR

Nel prompt dei comandi, ho eseguito le seguenti operazioni:

> chmod +x testenv.sh
> MY_VAR=12345
> ./testenv.sh

> source testenv.sh
12345
> MY_VAR=12345 ./testenv.sh
12345

[nota che il primo modulo ha restituito una stringa vuota]

Quindi, questo piccolo esperimento suggerisce che v'è una differenza, dopo tutto, dove per il comando di "fonte", l'ambiente figlio eredita tutte le variabili del Genitore unico, dove per il "" non è così.

Mi sto perdendo qualcosa o questa è una caratteristica non documentata / deprecata di bash ?

[GNU bash, versione 4.1.5 (1) -release (x86_64-pc-linux-gnu)]

Risposte:


68

Risposta breve

Nella tua domanda, il secondo comando non usa né l' .integrato della shell né quello sourceincorporato. Invece, stai effettivamente eseguendo lo script in una shell separata, invocandolo per nome come faresti con qualsiasi altro file eseguibile. Questo gli dà un insieme separato di variabili (anche se se esporti una variabile nella sua shell genitore, sarà una variabile d'ambiente per qualsiasi processo figlio , e quindi sarà inclusa nelle variabili di una shell figlio ). Se cambi il /in uno spazio, questo lo eseguirà con il .built-in, che è equivalente a source.

Spiegazione estesa

Questa è la sintassi della sourceshell integrata, che esegue il contenuto di uno script nella shell corrente (e quindi con le variabili della shell corrente):

source testenv.sh

Questa è la sintassi del .built-in, che fa la stessa cosa di source:

. testenv.sh

Tuttavia, questa sintassi esegue lo script come file eseguibile, avviando una nuova shell per eseguirlo:

./testenv.sh

Quello non sta usando il .built-in. Piuttosto, .fa parte del percorso del file che si sta eseguendo. In generale, è possibile eseguire qualsiasi file eseguibile in una shell invocandolo con un nome che contiene almeno un /carattere. Per eseguire un file nella directory corrente, precederlo ./è quindi il modo più semplice. A meno che la directory corrente non sia nella tua PATH, non puoi eseguire lo script con il comando testenv.sh. Questo per impedire alle persone di eseguire accidentalmente file nella directory corrente quando intendono eseguire un comando di sistema o qualche altro file esistente in alcune directory elencate nella PATHvariabile d'ambiente.

Poiché l'esecuzione di un file per nome (anziché con sourceo .) lo esegue in una nuova shell, avrà un proprio set di variabili shell. La nuova shell eredita le variabili di ambiente dal processo chiamante (che in questo caso è la shell interattiva) e tali variabili di ambiente diventano variabili di shell nella nuova shell. Tuttavia, affinché una variabile di shell sia passata alla nuova shell, è necessario che si verifichi una delle seguenti condizioni:

  1. La variabile shell è stata esportata, facendola diventare una variabile d'ambiente. Utilizzare la exportshell integrata per questo. Nel tuo esempio, puoi usare export MY_VAR=12345per impostare ed esportare la variabile in un solo passaggio, o se è già impostata puoi semplicemente usare export MY_VAR.

  2. La variabile shell viene esplicitamente impostata e passata per il comando in esecuzione, causando che sia una variabile di ambiente per la durata del comando in esecuzione. Questo di solito realizza che:

    MY_VAR=12345 ./testenv.sh

    Se MY_VARè una variabile di shell che non è stata esportata, puoi anche eseguirla testenv.shcon MY_VARpassata come variabile di ambiente impostandola su se stessa :

    MY_VAR="$MY_VAR" ./testenv.sh

./ La sintassi per gli script richiede una linea di Hashbang per funzionare (correttamente)

A proposito, si ricorda che, quando invoke un eseguibile per nome come sopra (e non con il .o sourceshell built-in), che cosa shell programma viene utilizzato per eseguirlo è non generalmente determinato da ciò che si sta eseguendo shell da . Anziché:

  • Per i file binari, il kernel può essere configurato per eseguire file di quel particolare tipo. Esamina i primi due byte del file per un "numero magico" che indica che tipo di eseguibile binario è. Ecco come possono essere eseguiti i file binari eseguibili.

    Questo è, ovviamente, estremamente importante, perché uno script non può essere eseguito senza una shell o un altro interprete, che è un file binario eseguibile! Inoltre, molti comandi e applicazioni sono binari compilati anziché script.

    ( #!è la rappresentazione testuale del "numero magico" che indica un eseguibile testuale.)

  • Per i file che dovrebbero essere eseguiti in una shell o in un altro linguaggio interpretato, la prima riga appare come:

    #!/bin/sh

    /bin/shpuò essere sostituito con qualunque altra shell o interprete inteso per eseguire il programma. Ad esempio, un programma Python potrebbe iniziare con la riga:

    #!/usr/bin/python

    Queste linee sono chiamate hashbang, shebang e numerosi altri nomi simili. Vedi questa voce FOLDOC , questo articolo di Wikipedia e # # / / bin / sh è letto dall'interprete? per maggiori informazioni.

  • Se un file di testo è contrassegnato come eseguibile e lo si esegue dalla shell (come ./filename) ma non inizia con #!, il kernel non riesce a eseguirlo. Tuttavia, visto che questo è successo, la tua shell proverà ad eseguirla passando il suo nome ad una shell. Esistono pochi requisiti su quale shell sia ( "la shell eseguirà un comando equivalente a far invocare una shell ..." ). In pratica , alcune shell - incluso bash* - eseguono un'altra istanza di se stesse, mentre altre usano/bin/sh. Consiglio vivamente di evitarlo e utilizzare invece una riga hashbang (o eseguire lo script passandolo all'interprete desiderato, ad esempio bash filename).

    * Manuale GNU Bash , 3.7.2 Ricerca comandi ed esecuzione : "Se questa esecuzione fallisce perché il file non è in formato eseguibile e il file non è una directory, si presume che sia uno script di shell e la shell lo esegue come descritto negli script Shell . "


2
Quello che ho trovato utile sourceè che le funzioni sono diventate disponibili da Bash senza bisogno di caricare o riavviare. Esempio #!/bin/bash function olakease {echo olakease;}. Una volta caricato, source file.shpuoi chiamare direttamente olakeaseda bash. Mi piace molto La sorgente esegue quindi molte cose, il punto .è solo per l'esecuzione ed è qualcosa di simile all'usobash file.sh
m3nda

2
Anche @ erm3nda .ha questo comportamento: . file.she source file.shfa esattamente la stessa cosa, incluse le funzioni di mantenimento definite in file.sh. (Forse stai pensando ./file.sh, che è diverso. Ma che non usa il .builtin; invece, .fa parte del percorso.)
Eliah Kagan,

Oh !, non ho letto attentamente il file. [Space]. Grazie mille mach.
m3nda,

13

Sì, ti stai perdendo qualcosa.

Penso che stai confondendo il '.' significa directory corrente, come in ./testenv.she il '.' ciò significa source(che è un comando integrato). Quindi, nel caso in cui '.' significa sourceche sarebbe . ./testenv.sh. Ha senso?

Quindi prova questo:

MY_VAR=12345 
. ./testenv.sh

2
Lo ./dice esattamente dove si trova il file, senza di esso, bash guarderà prima attraverso PATH, quindi prova la directory corrente se non la trova. Se bash è in esecuzione in modalità POSIX e non fornisci un percorso al file (come ./), cercherà solo in PATH e non riuscirà a trovare il file se la directory corrente non è in PATH.
geirha,

@geirha Sì, hai ragione source(e .) controlleranno prima $ PATH, anche se in realtà non eseguono lo script nel solito senso. Il mio (ex) commento non era corretto.
Eliah Kagan,

Breve e al punto +1
David Morales,
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.